From f34ba69a3d883ad53680fcf2a2cc8ba2f0f5fb89 Mon Sep 17 00:00:00 2001 From: Johann Wagner Date: Tue, 11 Jun 2024 20:20:14 +0200 Subject: [PATCH 01/21] feat: Initial implementation for OIDC with machines --- Cargo.lock | 1107 ++++++++++++++++++++++++++------- README.md | 23 + fairy/Cargo.toml | 3 + fairy/Rocket.example.toml | 8 +- fairy/src/api.rs | 112 ++++ fairy/src/main.rs | 77 +-- vicky/Rocket.example.toml | 4 - vicky/src/bin/vicky/auth.rs | 86 +-- vicky/src/bin/vicky/errors.rs | 3 + vicky/src/bin/vicky/locks.rs | 27 +- vicky/src/bin/vicky/main.rs | 15 +- vicky/src/bin/vicky/tasks.rs | 28 +- 12 files changed, 1121 insertions(+), 372 deletions(-) create mode 100644 fairy/src/api.rs diff --git a/Cargo.lock b/Cargo.lock index b9916d2..8cca400 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "addr2line" -version = "0.21.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" dependencies = [ "gimli", ] @@ -126,9 +126,9 @@ dependencies = [ [[package]] name = "anstyle-query" -version = "1.0.3" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5" +checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" dependencies = [ "windows-sys 0.52.0", ] @@ -145,9 +145,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.82" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "async-stream" @@ -197,6 +197,12 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" version = "1.3.0" @@ -217,9 +223,9 @@ dependencies = [ [[package]] name = "aws-runtime" -version = "1.2.2" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75588e7ee5e8496eed939adac2035a6dbab9f7eb2acdd9ab2d31856dab6f3955" +checksum = "36978815abdd7297662bf906adff132941a02ecf425bc78fac7d90653ce87560" dependencies = [ "aws-credential-types", "aws-sigv4", @@ -241,9 +247,9 @@ dependencies = [ [[package]] name = "aws-sdk-s3" -version = "1.34.0" +version = "1.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "724119d8fd2d2638b9979673f3b5c2979fa388c9ca27815e3cb5ad6234fac3f5" +checksum = "99e06a6cd8592e486f29c8af427c4083286cee4ea0e4ae46a164a24d07ee19d5" dependencies = [ "ahash", "aws-credential-types", @@ -276,9 +282,9 @@ dependencies = [ [[package]] name = "aws-sigv4" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58b56f1cbe6fd4d0c2573df72868f20ab1c125ca9c9dbce17927a463433a2e57" +checksum = "31eed8d45759b2c5fe7fd304dd70739060e9e0de509209036eabea14d0720cce" dependencies = [ "aws-credential-types", "aws-smithy-eventstream", @@ -293,7 +299,7 @@ dependencies = [ "http 0.2.12", "http 1.1.0", "once_cell", - "p256", + "p256 0.11.1", "percent-encoding", "ring", "sha2", @@ -316,9 +322,9 @@ dependencies = [ [[package]] name = "aws-smithy-checksums" -version = "0.60.9" +version = "0.60.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6242d6a54d3b4b83458f4abd7057ba93c4419dc71e8217e9acd3a748d656d99e" +checksum = "c5b30ea96823b8b25fb6471643a516e1bd475fd5575304e6240aea179f213216" dependencies = [ "aws-smithy-http", "aws-smithy-types", @@ -378,9 +384,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime" -version = "1.5.7" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8508de54f34b8feca6638466c2bd2de9d1df5bf79c578de9a649b72d644006b3" +checksum = "db83b08939838d18e33b5dbaf1a0f048f28c10bd28071ab7ce6f245451855414" dependencies = [ "aws-smithy-async", "aws-smithy-http", @@ -393,7 +399,7 @@ dependencies = [ "http-body 0.4.6", "http-body 1.0.0", "httparse", - "hyper 0.14.28", + "hyper 0.14.29", "hyper-rustls", "once_cell", "pin-project-lite", @@ -405,9 +411,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime-api" -version = "1.6.3" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa6dbabc7629fab4e4467f95f119c2e1a9b00b44c893affa98e23b040a0e2567" +checksum = "1b570ea39eb95bd32543f6e4032bce172cb6209b9bc8c83c770d08169e875afc" dependencies = [ "aws-smithy-async", "aws-smithy-types", @@ -472,9 +478,9 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.71" +version = "0.3.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" +checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" dependencies = [ "addr2line", "cc", @@ -491,6 +497,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + [[package]] name = "base64" version = "0.13.1" @@ -573,9 +585,9 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytemuck" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15" +checksum = "78834c15cb5d5efe3452d58b1e8ba890dd62d21907f867f383358198e56ebca5" [[package]] name = "byteorder" @@ -616,9 +628,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.97" +version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4" +checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695" [[package]] name = "cfg-if" @@ -634,16 +646,18 @@ checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "android-tzdata", "iana-time-zone", + "js-sys", "num-traits", "serde", + "wasm-bindgen", "windows-targets 0.52.5", ] [[package]] name = "chrono-tz" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d59ae0466b83e838b81a54256c39d5d7c20b9d7daa10510a242d9b75abd5936e" +checksum = "93698b29de5e97ad0ae26447b344c482a7284c737d9ddc5f9e52b74a336671bb" dependencies = [ "chrono", "chrono-tz-build", @@ -652,9 +666,9 @@ dependencies = [ [[package]] name = "chrono-tz-build" -version = "0.2.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "433e39f13c9a060046954e0592a8d0a4bcb1040125cbf91cb8ee58964cfb350f" +checksum = "0c088aee841df9c3041febbb73934cfc39708749bf96dc827e3359cd39ef11b1" dependencies = [ "parse-zoneinfo", "phf", @@ -673,9 +687,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.4" +version = "4.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" +checksum = "5db83dced34638ad474f39f250d7fea9598bdd239eaced1bdf45d597da0f433f" dependencies = [ "clap_builder", "clap_derive", @@ -683,21 +697,21 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.2" +version = "4.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" +checksum = "f7e204572485eb3fbf28f871612191521df159bc3e15a9f5064c66dba3a8c05f" dependencies = [ "anstream", "anstyle", "clap_lex", - "strsim 0.11.1", + "strsim", ] [[package]] name = "clap_derive" -version = "4.5.4" +version = "4.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" +checksum = "c780290ccf4fb26629baa7a1081e68ced113f1d3ec302fa5948f1c381ebf06c6" dependencies = [ "heck", "proc-macro2", @@ -707,9 +721,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" +checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" [[package]] name = "colorchoice" @@ -781,27 +795,27 @@ dependencies = [ [[package]] name = "crc32c" -version = "0.6.5" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89254598aa9b9fa608de44b3ae54c810f0f06d755e24c50177f1f8f31ff50ce2" +checksum = "3a47af21622d091a8f0fb295b88bc886ac74efcc613efc19f5d0b21de5c89e47" dependencies = [ "rustc_version", ] [[package]] name = "crc32fast" -version = "1.4.0" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ "cfg-if", ] [[package]] name = "crossbeam-channel" -version = "0.5.12" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab3db02a9c5b5121e1e42fbdb1aeb65f5e02624cc58c43f2884c6ccac0b82f95" +checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" dependencies = [ "crossbeam-utils", ] @@ -827,9 +841,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.19" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "crossterm" @@ -874,8 +888,10 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ + "generic-array", "rand_core", "subtle", + "zeroize", ] [[package]] @@ -898,11 +914,39 @@ dependencies = [ "cipher", ] +[[package]] +name = "curve25519-dalek" +version = "4.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a677b8922c94e01bdbb12126b0bc852f00447528dee1782229af9c720c3f348" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "platforms", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "darling" -version = "0.20.8" +version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391" +checksum = "83b2eb4d90d12bdda5ed17de686c2acb4c57914f8f921b8da7e112b5a36f3fe1" dependencies = [ "darling_core", "darling_macro", @@ -910,23 +954,23 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.8" +version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f" +checksum = "622687fe0bac72a04e5599029151f5796111b90f1baaa9b544d807a5e31cd120" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", - "strsim 0.10.0", + "strsim", "syn", ] [[package]] name = "darling_macro" -version = "0.20.8" +version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" +checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178" dependencies = [ "darling_core", "quote", @@ -943,6 +987,17 @@ dependencies = [ "zeroize", ] +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + [[package]] name = "deranged" version = "0.3.11" @@ -986,9 +1041,9 @@ dependencies = [ [[package]] name = "deunicode" -version = "1.4.4" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322ef0094744e63628e6f0eb2295517f79276a5b342a4c2ff3042566ca181d4e" +checksum = "339544cc9e2c4dc3fc7149fd630c5f22263a4fdf18a98afd0075784968b5cf00" [[package]] name = "devise" @@ -1025,9 +1080,9 @@ dependencies = [ [[package]] name = "diesel" -version = "2.2.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35b696af9ff4c0d2a507db2c5faafa8aa0205e297e5f11e203a24226d5355e7a" +checksum = "62d6dcd069e7b5fe49a302411f759d4cf1cf2c27fe798ef46fb8baefc053dd2b" dependencies = [ "bitflags 2.5.0", "byteorder", @@ -1040,9 +1095,9 @@ dependencies = [ [[package]] name = "diesel_derives" -version = "2.2.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d6fdd83d5947068817016e939596d246e5367279453f2a3433287894f2f2996" +checksum = "59de76a222c2b8059f789cbe07afbfd8deb8c31dd0bc2a21f85e256c1def8259" dependencies = [ "diesel_table_macro_syntax", "dsl_auto_type", @@ -1078,15 +1133,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", + "const-oid", "crypto-common", "subtle", ] +[[package]] +name = "displaydoc" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "dsl_auto_type" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab32c18ea6760d951659768a3e35ea72fc1ba0916d665a88dfe048b2a41e543f" +checksum = "0892a17df262a24294c382f0d5997571006e7a4348b4327557c4ff1cd4a8bccc" dependencies = [ "darling", "either", @@ -1096,23 +1163,67 @@ dependencies = [ "syn", ] +[[package]] +name = "dyn-clone" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" + [[package]] name = "ecdsa" version = "0.14.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c" dependencies = [ - "der", - "elliptic-curve", - "rfc6979", - "signature", + "der 0.6.1", + "elliptic-curve 0.12.3", + "rfc6979 0.3.1", + "signature 1.6.4", +] + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der 0.7.9", + "digest", + "elliptic-curve 0.13.8", + "rfc6979 0.4.0", + "signature 2.2.0", + "spki 0.7.3", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8 0.10.2", + "signature 2.2.0", +] + +[[package]] +name = "ed25519-dalek" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" +dependencies = [ + "curve25519-dalek", + "ed25519", + "serde", + "sha2", + "subtle", + "zeroize", ] [[package]] name = "either" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" +checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" [[package]] name = "elliptic-curve" @@ -1120,16 +1231,37 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" dependencies = [ - "base16ct", + "base16ct 0.1.1", "crypto-bigint 0.4.9", - "der", + "der 0.6.1", + "digest", + "ff 0.12.1", + "generic-array", + "group 0.12.1", + "pkcs8 0.9.0", + "rand_core", + "sec1 0.3.0", + "subtle", + "zeroize", +] + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct 0.2.0", + "crypto-bigint 0.5.5", "digest", - "ff", + "ff 0.13.0", "generic-array", - "group", - "pkcs8", + "group 0.13.0", + "hkdf", + "pem-rfc7468", + "pkcs8 0.10.2", "rand_core", - "sec1", + "sec1 0.7.3", "subtle", "zeroize", ] @@ -1187,9 +1319,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", "windows-sys 0.52.0", @@ -1200,10 +1332,13 @@ name = "fairy" version = "0.1.0" dependencies = [ "anyhow", + "chrono", "env_logger 0.10.2", "futures-util", - "hyper 0.14.28", + "hyper 0.14.29", "log", + "openidconnect", + "reqwest 0.12.4", "rocket", "serde", "serde_json", @@ -1230,11 +1365,27 @@ dependencies = [ "subtle", ] +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + [[package]] name = "figment" -version = "0.10.18" +version = "0.10.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d032832d74006f99547004d49410a4b4218e4c33382d56ca3ff89df74f86b953" +checksum = "8cb01cd46b0cf372153850f4c6c272d9cbea2da513e07538405148f95bd789f3" dependencies = [ "atomic 0.6.0", "pear", @@ -1393,6 +1544,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -1402,8 +1554,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -1418,9 +1572,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.1" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" [[package]] name = "glob" @@ -1437,17 +1591,17 @@ dependencies = [ "aho-corasick", "bstr", "log", - "regex-automata 0.4.6", - "regex-syntax 0.8.3", + "regex-automata 0.4.7", + "regex-syntax 0.8.4", ] [[package]] name = "globwalk" -version = "0.8.1" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc" +checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.5.0", "ignore", "walkdir", ] @@ -1458,7 +1612,18 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" dependencies = [ - "ff", + "ff 0.12.1", + "rand_core", + "subtle", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff 0.13.0", "rand_core", "subtle", ] @@ -1484,15 +1649,15 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "816ec7294445779408f36fe57bc5b7fc1cf59664059096c65f905c1c61f58069" +checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab" dependencies = [ + "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", - "futures-util", "http 1.1.0", "indexmap 2.2.6", "slab", @@ -1607,12 +1772,12 @@ dependencies = [ [[package]] name = "http-body-util" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" dependencies = [ "bytes", - "futures-core", + "futures-util", "http 1.1.0", "http-body 1.0.0", "pin-project-lite", @@ -1647,9 +1812,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.28" +version = "0.14.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" +checksum = "f361cde2f109281a220d4307746cdfd5ee3f410da58a70377762396775634b33" dependencies = [ "bytes", "futures-channel", @@ -1678,7 +1843,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "h2 0.4.4", + "h2 0.4.5", "http 1.1.0", "http-body 1.0.0", "httparse", @@ -1697,7 +1862,7 @@ checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", "http 0.2.12", - "hyper 0.14.28", + "hyper 0.14.29", "log", "rustls", "rustls-native-certs", @@ -1712,7 +1877,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ "bytes", - "hyper 0.14.28", + "hyper 0.14.29", "native-tls", "tokio", "tokio-native-tls", @@ -1736,9 +1901,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.3" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" +checksum = "7b875924a60b96e5d7b9ae7b066540b1dd1cbd90d1828f54c92e02a283351c56" dependencies = [ "bytes", "futures-channel", @@ -1777,6 +1942,124 @@ dependencies = [ "cc", ] +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f8ac670d7422d7f76b32e17a5db556510825b29ec9154f235977c9caba61036" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -1785,12 +2068,14 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "0.5.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +checksum = "4716a3a0933a1d01c2f72450e89596eb51dd34ef3c211ccd875acdf1f8fe47ed" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "icu_normalizer", + "icu_properties", + "smallvec", + "utf8_iter", ] [[package]] @@ -1803,7 +2088,7 @@ dependencies = [ "globset", "log", "memchr", - "regex-automata 0.4.6", + "regex-automata 0.4.7", "same-file", "walkdir", "winapi-util", @@ -1889,6 +2174,15 @@ version = "1.70.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.12.1" @@ -1965,12 +2259,15 @@ name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin 0.5.2", +] [[package]] name = "libc" -version = "0.2.154" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "libm" @@ -1980,9 +2277,15 @@ checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" [[package]] name = "linux-raw-sys" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "litemap" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" [[package]] name = "lock_api" @@ -2045,9 +2348,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.2" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "migrations_internals" @@ -2078,9 +2381,9 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "miniz_oxide" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae" dependencies = [ "adler", ] @@ -2110,7 +2413,7 @@ dependencies = [ "httparse", "memchr", "mime", - "spin", + "spin 0.9.8", "tokio", "tokio-util", "version_check", @@ -2118,11 +2421,10 @@ dependencies = [ [[package]] name = "native-tls" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" dependencies = [ - "lazy_static", "libc", "log", "openssl", @@ -2172,6 +2474,23 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "smallvec", + "zeroize", +] + [[package]] name = "num-conv" version = "0.1.0" @@ -2187,6 +2506,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -2194,6 +2524,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -2206,11 +2537,31 @@ dependencies = [ "libc", ] +[[package]] +name = "oauth2" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c38841cdd844847e3e7c8d29cef9dcfed8877f8f56f9071f77843ecf3baf937f" +dependencies = [ + "base64 0.13.1", + "chrono", + "getrandom", + "http 0.2.12", + "rand", + "reqwest 0.11.27", + "serde", + "serde_json", + "serde_path_to_error", + "sha2", + "thiserror", + "url", +] + [[package]] name = "object" -version = "0.32.2" +version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +checksum = "576dfe1fc8f9df304abb159d767a29d0476f7750fbf8aa7ad07816004a207434" dependencies = [ "memchr", ] @@ -2227,6 +2578,38 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +[[package]] +name = "openidconnect" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f47e80a9cfae4462dd29c41e987edd228971d6565553fbc14b8a11e666d91590" +dependencies = [ + "base64 0.13.1", + "chrono", + "dyn-clone", + "ed25519-dalek", + "hmac", + "http 0.2.12", + "itertools 0.10.5", + "log", + "oauth2", + "p256 0.13.2", + "p384", + "rand", + "rsa", + "serde", + "serde-value", + "serde_derive", + "serde_json", + "serde_path_to_error", + "serde_plain", + "serde_with", + "sha2", + "subtle", + "thiserror", + "url", +] + [[package]] name = "openssl" version = "0.10.64" @@ -2271,6 +2654,15 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "ordered-float" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" +dependencies = [ + "num-traits", +] + [[package]] name = "outref" version = "0.5.1" @@ -2289,16 +2681,40 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51f44edd08f51e2ade572f141051021c5af22677e42b7dd28a88155151c33594" dependencies = [ - "ecdsa", - "elliptic-curve", + "ecdsa 0.14.8", + "elliptic-curve 0.12.3", + "sha2", +] + +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa 0.16.9", + "elliptic-curve 0.13.8", + "primeorder", + "sha2", +] + +[[package]] +name = "p384" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70786f51bcc69f6a4c0360e063a4cac5419ef7c5cd5b3c99ad70f3be5ba79209" +dependencies = [ + "ecdsa 0.16.9", + "elliptic-curve 0.13.8", + "primeorder", "sha2", ] [[package]] name = "parking_lot" -version = "0.12.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", "parking_lot_core", @@ -2312,7 +2728,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.1", + "redox_syscall 0.5.2", "smallvec", "windows-targets 0.52.5", ] @@ -2355,6 +2771,15 @@ dependencies = [ "syn", ] +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -2476,14 +2901,35 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der 0.7.9", + "pkcs8 0.10.2", + "spki 0.7.3", +] + [[package]] name = "pkcs8" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" dependencies = [ - "der", - "spki", + "der 0.6.1", + "spki 0.6.0", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der 0.7.9", + "spki 0.7.3", ] [[package]] @@ -2492,6 +2938,12 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +[[package]] +name = "platforms" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db23d408679286588f4d4644f965003d056e3dd5abcaaa938116871d7ce2fee7" + [[package]] name = "polyval" version = "0.6.2" @@ -2518,18 +2970,27 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "pq-sys" -version = "0.4.8" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31c0052426df997c0cbd30789eb44ca097e3541717a7b8fa36b1c464ee7edebd" +checksum = "a24ff9e4cf6945c988f0db7005d87747bf72864965c3529d259ad155ac41d584" dependencies = [ "vcpkg", ] +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve 0.13.8", +] + [[package]] name = "proc-macro2" -version = "1.0.81" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" +checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" dependencies = [ "unicode-ident", ] @@ -2644,27 +3105,27 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" +checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd" dependencies = [ "bitflags 2.5.0", ] [[package]] name = "ref-cast" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4846d4c50d1721b1a3bef8af76924eef20d5e723647333798c1b519b3a9473f" +checksum = "ccf0a6f84d5f1d581da8b41b47ec8600871962f2a528115b542b362d4b744931" dependencies = [ "ref-cast-impl", ] [[package]] name = "ref-cast-impl" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fddb4f8d99b0a2ebafc65a87a69a7b9875e4b1ae1f00db265d300ef7f28bccc" +checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6" dependencies = [ "proc-macro2", "quote", @@ -2673,14 +3134,14 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.4" +version = "1.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.6", - "regex-syntax 0.8.3", + "regex-automata 0.4.7", + "regex-syntax 0.8.4", ] [[package]] @@ -2694,13 +3155,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.3", + "regex-syntax 0.8.4", ] [[package]] @@ -2717,9 +3178,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] name = "reqwest" @@ -2735,7 +3196,8 @@ dependencies = [ "h2 0.3.26", "http 0.2.12", "http-body 0.4.6", - "hyper 0.14.28", + "hyper 0.14.29", + "hyper-rustls", "hyper-tls 0.5.0", "ipnet", "js-sys", @@ -2745,6 +3207,7 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", + "rustls", "rustls-pemfile 1.0.4", "serde", "serde_json", @@ -2753,11 +3216,13 @@ dependencies = [ "system-configuration", "tokio", "tokio-native-tls", + "tokio-rustls", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", + "webpki-roots", "winreg 0.50.0", ] @@ -2773,7 +3238,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2 0.4.4", + "h2 0.4.5", "http 1.1.0", "http-body 1.0.0", "http-body-util", @@ -2815,6 +3280,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "ring" version = "0.17.8" @@ -2825,7 +3300,7 @@ dependencies = [ "cfg-if", "getrandom", "libc", - "spin", + "spin 0.9.8", "untrusted", "windows-sys 0.52.0", ] @@ -2908,7 +3383,7 @@ dependencies = [ "either", "futures", "http 0.2.12", - "hyper 0.14.28", + "hyper 0.14.29", "indexmap 2.2.6", "log", "memchr", @@ -2950,11 +3425,31 @@ dependencies = [ "quote", ] +[[package]] +name = "rsa" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8 0.10.2", + "rand_core", + "signature 2.2.0", + "spki 0.7.3", + "subtle", + "zeroize", +] + [[package]] name = "rustc-demangle" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc_version" @@ -3039,15 +3534,15 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80af6f9131f277a45a3fba6ce8e2258037bb0477a67e610d3c1fe046ab31de47" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" [[package]] name = "ryu" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "same-file" @@ -3104,10 +3599,24 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" dependencies = [ - "base16ct", - "der", + "base16ct 0.1.1", + "der 0.6.1", + "generic-array", + "pkcs8 0.9.0", + "subtle", + "zeroize", +] + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct 0.2.0", + "der 0.7.9", "generic-array", - "pkcs8", + "pkcs8 0.10.2", "subtle", "zeroize", ] @@ -3137,24 +3646,34 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.200" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddc6f9cc94d67c0e21aaf7eda3a010fd3af78ebf6e096aa6e2e13c79749cce4f" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" dependencies = [ "serde_derive", ] +[[package]] +name = "serde-value" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" +dependencies = [ + "ordered-float", + "serde", +] + [[package]] name = "serde_derive" -version = "1.0.200" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", @@ -3163,20 +3682,39 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.116" +version = "1.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" +checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" dependencies = [ "itoa", "ryu", "serde", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" +dependencies = [ + "itoa", + "serde", +] + +[[package]] +name = "serde_plain" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1fc6db65a611022b23a0dec6975d63fb80a302cb3388835ff02c097258d50" +dependencies = [ + "serde", +] + [[package]] name = "serde_spanned" -version = "0.6.5" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" +checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" dependencies = [ "serde", ] @@ -3307,6 +3845,16 @@ dependencies = [ "rand_core", ] +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core", +] + [[package]] name = "siphasher" version = "0.3.11" @@ -3348,6 +3896,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + [[package]] name = "spin" version = "0.9.8" @@ -3361,7 +3915,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" dependencies = [ "base64ct", - "der", + "der 0.6.1", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der 0.7.9", ] [[package]] @@ -3383,6 +3947,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "state" version = "0.6.0" @@ -3398,12 +3968,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - [[package]] name = "strsim" version = "0.11.1" @@ -3421,9 +3985,9 @@ dependencies = [ [[package]] name = "strum_macros" -version = "0.26.3" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7993a8e3a9e88a00351486baae9522c91b123a088f76469e5bd5cc17198ea87" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" dependencies = [ "heck", "proc-macro2", @@ -3440,9 +4004,9 @@ checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "syn" -version = "2.0.60" +version = "2.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" +checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" dependencies = [ "proc-macro2", "quote", @@ -3455,6 +4019,17 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "system-configuration" version = "0.5.1" @@ -3490,9 +4065,9 @@ dependencies = [ [[package]] name = "tera" -version = "1.19.1" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "970dff17c11e884a4a09bc76e3a17ef71e01bb13447a11e85226e254fe6d10b8" +checksum = "ab9d851b45e865f178319da0abdbfe6acbc4328759ff18dafc3a41c16b4cd2ee" dependencies = [ "chrono", "chrono-tz", @@ -3581,25 +4156,20 @@ dependencies = [ ] [[package]] -name = "tinyvec" -version = "1.6.0" +name = "tinystr" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" dependencies = [ - "tinyvec_macros", + "displaydoc", + "zerovec", ] -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - [[package]] name = "tokio" -version = "1.37.0" +version = "1.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" +checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" dependencies = [ "backtrace", "bytes", @@ -3616,9 +4186,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" dependencies = [ "proc-macro2", "quote", @@ -3671,9 +4241,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.12" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3" +checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335" dependencies = [ "serde", "serde_spanned", @@ -3683,18 +4253,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.5" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.22.12" +version = "0.22.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3328d4f68a705b2a4498da1d580585d39a6510f98318a2cec3018a7ec61ddef" +checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38" dependencies = [ "indexmap 2.2.6", "serde", @@ -3716,7 +4286,6 @@ dependencies = [ "tokio", "tower-layer", "tower-service", - "tracing", ] [[package]] @@ -3737,7 +4306,6 @@ version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -3880,27 +4448,12 @@ dependencies = [ "unic-common", ] -[[package]] -name = "unicode-bidi" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" - [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" -[[package]] -name = "unicode-normalization" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" -dependencies = [ - "tinyvec", -] - [[package]] name = "unicode-segmentation" version = "1.11.0" @@ -3919,9 +4472,9 @@ dependencies = [ [[package]] name = "unicode-width" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6" +checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" [[package]] name = "unicode-xid" @@ -3953,20 +4506,33 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.0" +version = "2.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +checksum = "f7c25da092f0a868cdf09e8674cd3b7ef3a7d92a24253e663a2fb85e2496de56" dependencies = [ "form_urlencoded", "idna", "percent-encoding", + "serde", ] +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" @@ -4148,6 +4714,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-roots" +version = "0.25.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" + [[package]] name = "which" version = "6.0.1" @@ -4350,9 +4922,9 @@ checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" [[package]] name = "winnow" -version = "0.6.8" +version = "0.6.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3c52e9c97a68071b23e836c9380edae937f17b9c4667bd021973efc689f618d" +checksum = "59b5e5f6c299a3c7890b876a2a587f3115162487e704907d9b6cd29473052ba1" dependencies = [ "memchr", ] @@ -4383,6 +4955,18 @@ version = "0.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + [[package]] name = "xmlparser" version = "0.13.6" @@ -4398,6 +4982,30 @@ dependencies = [ "is-terminal", ] +[[package]] +name = "yoke" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + [[package]] name = "zerocopy" version = "0.7.34" @@ -4418,8 +5026,51 @@ dependencies = [ "syn", ] +[[package]] +name = "zerofrom" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + [[package]] name = "zeroize" -version = "1.7.0" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[package]] +name = "zerovec" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb2cc8827d6c0994478a15c53f374f46fbd41bea663d809b14744bc42e6b109c" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +checksum = "97cf56601ee5052b4417d90c8755c6683473c926039908196cf35d99f893ebe7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/README.md b/README.md index a04805b..b1f6623 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,7 @@ TODO @yu-re-ka: Add Information + Add own machine token to configuration + This is needed for `fairy` later. + Add OIDC authentication provider to configuration + + See OIDC provider section. + Enter `vicky` + Run `cargo run --bin vicky` @@ -89,3 +90,25 @@ Options: -V, --version Print version ``` +## OIDC Provider + +Since implementing user, role and account management is timeconsuming, we settled on fully using OIDC flows for this application. +Therefore, there is some configuration required. +This is tested against Keycloak instances. Your mileage may vary on other implementations. + +### Configuration + +Configuration is done via a well-known OIDC endpoint, e.g. `https://my-nice-keycloak-instance.com/realms/wobcom/.well-known/openid-configuration`. + +You need two different clients, one client which acts as a service account for your backend services and one client to authenticate your users against using the web interface. Every user authenticating with the backend client gets the role `vicky:machine`, everyone else gets the role `vicky:user`. + +We expected the following keys in the userinfo endpoint: ++ `vicky:user` + + TBD + + `vicky_roles` + + List of assigned roles, some of `vicky:machine` or `vicky:user`. ++ `vicky:machine` + + `sub` + + `preferred_username` + + `vicky_roles` + + List of assigned roles, some of `vicky:machine` or `vicky:user`. \ No newline at end of file diff --git a/fairy/Cargo.toml b/fairy/Cargo.toml index 6ee51d4..b5f9fd5 100644 --- a/fairy/Cargo.toml +++ b/fairy/Cargo.toml @@ -19,6 +19,9 @@ tokio-util = { version = "0.7.9", features = ["codec"] } uuid = { version = "1.4.1", features = ["serde"] } rocket = { version="0.5.0", features = ["json", "secrets"] } which = "6.0.1" +openidconnect = "3.5.0" +reqwest = { version="0.12.4", features = ["json"]} +chrono = "0.4.38" [features] nixless-test-mode = [] \ No newline at end of file diff --git a/fairy/Rocket.example.toml b/fairy/Rocket.example.toml index f3e59d9..fe0960d 100644 --- a/fairy/Rocket.example.toml +++ b/fairy/Rocket.example.toml @@ -2,5 +2,9 @@ vicky_url = "http://localhost:8000" vicky_external_url = "https://vicky.lab.wobcom.de" -machine_token = "" -features = [] \ No newline at end of file +features = [] + +[default.oidc_config] +authority = "https://id.lab.wobcom.de/realms/wobcom" +client_id = "vicky-dev-api" +client_secret = "" diff --git a/fairy/src/api.rs b/fairy/src/api.rs new file mode 100644 index 0000000..366ae54 --- /dev/null +++ b/fairy/src/api.rs @@ -0,0 +1,112 @@ +use std::sync::Arc; + +use chrono::{Utc, DateTime, Duration}; +use log::info; +use openidconnect::core::{CoreClient, CoreProviderMetadata}; +use openidconnect::{ClientId, ClientSecret, IssuerUrl, OAuth2TokenResponse, Scope}; +use serde::de::DeserializeOwned; +use serde::{Serialize}; +use reqwest::{self, Method, RequestBuilder}; +use openidconnect::reqwest::async_http_client; + + + +use crate::AppConfig; + +#[derive(Debug)] +pub enum HttpClientState { + Authenticated { + access_token: String, + expires_at: DateTime, + }, + Unauthenticated, +} +#[derive(Debug)] +pub struct HttpClient { + app_config: Arc, + http_client: reqwest::Client, + client_state: HttpClientState +} + +impl HttpClient { + pub fn new(cfg: Arc) -> HttpClient { + HttpClient { + app_config: cfg, + http_client: reqwest::Client::new(), + client_state: HttpClientState::Unauthenticated, + } + } + + async fn renew_access_token(&mut self) -> anyhow::Result { + let client_id = ClientId::new(self.app_config.oidc_config.client_id.clone()); + let client_secret = ClientSecret::new(self.app_config.oidc_config.client_secret.clone()); + let issuer_url = IssuerUrl::new(self.app_config.oidc_config.issuer_url.clone())?; + + info!("Using {:?} as client_id to try to authorize to {:?}..", client_id, issuer_url); + + let provider_metadata = CoreProviderMetadata::discover_async(issuer_url, &async_http_client).await?; + let client = CoreClient::from_provider_metadata( + provider_metadata, + client_id, + Some(client_secret), + ); + + let ccreq = client + .exchange_client_credentials() + .add_scope(Scope::new("openid".to_string())); + + let ccres = ccreq.request_async(async_http_client).await?; + + let access_token = ccres.access_token().secret(); + let expires_at = Utc::now() + ccres.expires_in().unwrap() - Duration::seconds(5); + + info!("Accquired access token, expiring at {:?} ..", expires_at); + + self.client_state = HttpClientState::Authenticated { access_token: access_token.clone(), expires_at }; + Ok(access_token.clone()) + } + + async fn create_request(&mut self, method: Method, url: U) -> anyhow::Result { + + let now = Utc::now(); + + info!("client_state: {:?}", self.client_state); + + let access_token_to_use = match &self.client_state { + HttpClientState::Authenticated { expires_at, access_token } => { + if expires_at > &now { + access_token.to_string() + } else { + self.renew_access_token().await? + } + }, + HttpClientState::Unauthenticated => { + self.renew_access_token().await? + }, + }; + + Ok(self.http_client.request(method, url).header("Authorization", format!("Bearer {}", access_token_to_use))) + } + + + pub async fn do_request( + &mut self, + method: Method, + endpoint: &str, + q: &BODY, + ) -> anyhow::Result { + + let response = self + .create_request(method, format!("{}/{}", self.app_config.vicky_url, endpoint)).await? + .header("content-type", "application/json") + .json(q) + .send().await?; + + + if !response.status().is_success() { + anyhow::bail!("API error: {:?}", response); + } + + Ok(response.json().await?) + } +} diff --git a/fairy/src/main.rs b/fairy/src/main.rs index 32c95c2..a63f777 100644 --- a/fairy/src/main.rs +++ b/fairy/src/main.rs @@ -2,23 +2,36 @@ use std::process::{exit, Stdio}; use std::sync::Arc; use anyhow::anyhow; +use api::HttpClient; use futures_util::{Sink, StreamExt, TryStreamExt}; use hyper::{Body, Client, Method, Request}; -use rocket::figment::{Figment, Profile}; -use rocket::figment::providers::{Env, Format, Toml}; use serde::{Deserialize, Serialize}; use serde::de::DeserializeOwned; use tokio::process::Command; use tokio_util::codec::{FramedRead, LinesCodec}; use uuid::Uuid; use which::which; +use reqwest::{self, Method}; + +use rocket::figment::providers::{Env, Format, Toml}; +use rocket::figment::{Figment, Profile}; -#[derive(Deserialize)] +mod api; + + +#[derive(Deserialize, Debug)] +pub struct OIDCConfig { + issuer_url: String, + client_id: String, + client_secret: String, +} + +#[derive(Deserialize, Debug)] pub(crate) struct AppConfig { pub(crate) vicky_url: String, pub(crate) vicky_external_url: String, - pub(crate) machine_token: String, pub(crate) features: Vec, + pub(crate) oidc_config: OIDCConfig, } const CODE_NIX_NOT_INSTALLED: i32 = 1; @@ -58,31 +71,7 @@ fn main() -> anyhow::Result<()> { run(app_config) } -async fn api( - cfg: &AppConfig, - method: Method, - endpoint: &str, - q: &BODY, -) -> anyhow::Result { - let client = Client::new(); - let req_data = serde_json::to_vec(q)?; - - let request = Request::builder() - .uri(format!("{}/{}", cfg.vicky_url, endpoint)) - .method(method) - .header("content-type", "application/json") - .header("authorization", &cfg.machine_token) - .body(Body::from(req_data))?; - - let response = client.request(request).await?; - - if !response.status().is_success() { - anyhow::bail!("API error: {:?}", response); - } - let resp_data = hyper::body::to_bytes(response.into_body()).await?; - Ok(serde_json::from_slice(&resp_data)?) -} #[derive(Debug, Deserialize)] pub struct FlakeRef { @@ -117,11 +106,11 @@ fn log_sink( cfg: Arc, task_id: Uuid, ) -> impl Sink, Error = anyhow::Error> + Send { - futures_util::sink::unfold((), move |_, lines: Vec| { - let cfg = cfg.clone(); + let vicky_client_task = HttpClient::new(cfg.clone()); + + futures_util::sink::unfold(vicky_client_task, move |mut http_client, lines: Vec| { async move { - let response = api::<_, ()>( - &cfg, + let response = http_client.do_request::<_, ()>( Method::POST, &format!("api/v1/tasks/{}/logs", task_id), &serde_json::json!({ "lines": lines }), @@ -131,7 +120,7 @@ fn log_sink( match response { Ok(_) => { log::info!("logged {} line(s) from task", lines.len()); - Ok(()) + Ok(http_client) } Err(e) => { log::error!( @@ -152,7 +141,6 @@ async fn try_run_task(cfg: Arc, task: &Task) -> anyhow::Result<()> { let mut child = Command::new("nix") .args(args) .env("VICKY_API_URL", &cfg.vicky_external_url) - .env("VICKY_MACHINE_TOKEN", &cfg.machine_token) .kill_on_drop(true) .stdin(Stdio::null()) .stdout(Stdio::piped()) @@ -183,6 +171,9 @@ async fn try_run_task(cfg: Arc, task: &Task) -> anyhow::Result<()> { } async fn run_task(cfg: Arc, task: Task) { + + let mut vicky_client_task = HttpClient::new(cfg.clone()); + #[cfg(not(feature = "nixless-test-mode"))] let result = match try_run_task(cfg.clone(), &task).await { Err(e) => { @@ -191,13 +182,12 @@ async fn run_task(cfg: Arc, task: Task) { } Ok(_) => TaskResult::Success, }; - + #[cfg(feature = "nixless-test-mode")] let result = TaskResult::Success; - + tokio::time::sleep(std::time::Duration::from_secs(1)).await; - let _ = api::<_, ()>( - &cfg, + let _ = vicky_client_task.do_request::<_, ()>( Method::POST, &format!("api/v1/tasks/{}/finish", task.id), &serde_json::json!({ "result": result }), @@ -205,10 +195,9 @@ async fn run_task(cfg: Arc, task: Task) { .await; } -async fn try_claim(cfg: Arc) -> anyhow::Result<()> { +async fn try_claim(cfg: Arc, vicky_client: &mut HttpClient) -> anyhow::Result<()> { log::debug!("trying to claim task..."); - if let Some(task) = api::<_, Option>( - &cfg, + if let Some(task) = vicky_client.do_request::<_, Option>( Method::POST, "api/v1/tasks/claim", &serde_json::json!({ "features": cfg.features }), @@ -228,12 +217,14 @@ async fn try_claim(cfg: Arc) -> anyhow::Result<()> { #[tokio::main(flavor = "current_thread")] async fn run(cfg: AppConfig) -> anyhow::Result<()> { + let cfg = Arc::new(cfg); + let mut vicky_client_mgmt = HttpClient::new(cfg.clone()); + log::info!("config valid, starting communication with vicky"); log::info!("waiting for tasks..."); - let cfg = Arc::new(cfg); loop { - if let Err(e) = try_claim(cfg.clone()).await { + if let Err(e) = try_claim(cfg.clone(), &mut vicky_client_mgmt).await { log::error!("{}", e); tokio::time::sleep(std::time::Duration::from_secs(5)).await; } diff --git a/vicky/Rocket.example.toml b/vicky/Rocket.example.toml index ba0cb58..08e3636 100644 --- a/vicky/Rocket.example.toml +++ b/vicky/Rocket.example.toml @@ -1,9 +1,5 @@ [default] -machines = [ - "abc1234" -] - [default.databases] postgres_db = { url = "postgres://vicky:vicky@localhost/vicky" } diff --git a/vicky/src/bin/vicky/auth.rs b/vicky/src/bin/vicky/auth.rs index 7b6f2f2..187cb80 100644 --- a/vicky/src/bin/vicky/auth.rs +++ b/vicky/src/bin/vicky/auth.rs @@ -11,13 +11,26 @@ use vickylib::database::entities::Database; use vickylib::database::entities::user::db_impl::{UserDatabase, DbUser}; -use crate::{Config, OIDCConfigResolved}; +use crate::{OIDCConfigResolved}; use crate::errors::AppError; #[derive(Deserialize, Clone)] #[serde(rename_all = "lowercase")] pub enum Role { Admin, + Machine +} + +impl FromStr for Role { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "vicky:admin" => Ok(Self::Admin), + "vicky:machine" => Ok(Self::Machine), + _ => Err(()), + } + } } #[allow(dead_code)] @@ -53,20 +66,45 @@ async fn extract_user_from_token(jwks_verifier: &State, db: let user_info = x.json::().await?; - let name = match user_info.get("name").and_then(|x| x.as_str()) { - Some(name) => Some(name), - None => return Err(AppError::JWTFormatError("user_info must contain name".to_string())) - }; + log::info!("userinfo={:?}", user_info); + + let vicky_role = user_info.get("vicky_roles").and_then(|x| x.as_str()); - let new_user = DbUser { - sub: sub.unwrap(), - name: name.unwrap().to_string(), - role: "ADMIN".to_string(), - }; + let account_user: DbUser; + + match vicky_role { + Some(vicky_role) if vicky_role == "vicky:machine" => { + let preferred_username = match user_info.get("preferred_username").and_then(|x| x.as_str()) { + Some(preferred_username) => Some(preferred_username), + None => return Err(AppError::JWTFormatError("user_info must contain preferred_username".to_string())) + }; - let new_user_create = new_user.clone(); + account_user = DbUser { + sub: sub.unwrap(), + name: preferred_username.unwrap().to_string(), + role: vicky_role.to_string(), + }; + } + Some(vicky_role) if vicky_role == "vicky:admin" => { + let name = match user_info.get("name").and_then(|x| x.as_str()) { + Some(name) => Some(name), + None => return Err(AppError::JWTFormatError("user_info must contain name".to_string())) + }; + + account_user = DbUser { + sub: sub.unwrap(), + name: name.unwrap().to_string(), + role: vicky_role.to_string(), + }; + } + _ => { + return Err(AppError::UserAccountError("vicky_roles was not filled".to_string())) + } + } + + let new_user_create = account_user.clone(); db.run(move |conn| conn.upsert_user(new_user_create)).await?; - Ok(new_user) + Ok(account_user) } } } @@ -104,7 +142,7 @@ impl<'r> request::FromRequest<'r> for User { let user = User { id: user.sub, full_name: user.name, - role: Role::Admin + role: Role::from_str(&user.role).unwrap() }; request::Outcome::Success(user) @@ -121,25 +159,3 @@ impl<'r> request::FromRequest<'r> for User { } } -#[rocket::async_trait] -impl<'r> request::FromRequest<'r> for Machine { - type Error = (); - - async fn from_request(request: &'r request::Request<'_>) -> request::Outcome { - let config = request - .guard::<&State>() - .await - .expect("request Config"); - - if let Some(auth_header) = request.headers().get_one("Authorization") { - let cfg_user = config.machines.iter().find(|x| *x == auth_header); - - return match cfg_user { - Some(_) => request::Outcome::Success(Machine {}), - None => request::Outcome::Error((Status::Forbidden, ())), - }; - } - - request::Outcome::Forward(Status::Forbidden) - } -} diff --git a/vicky/src/bin/vicky/errors.rs b/vicky/src/bin/vicky/errors.rs index 28c8d64..e85187f 100644 --- a/vicky/src/bin/vicky/errors.rs +++ b/vicky/src/bin/vicky/errors.rs @@ -41,6 +41,9 @@ pub enum AppError { #[error("JWTFormatError {0:?}")] JWTFormatError(String), + #[error("UserAccountError {0:?}")] + UserAccountError(String), + #[error("ReqwestError {source:?}")] ReqwestError { #[from] diff --git a/vicky/src/bin/vicky/locks.rs b/vicky/src/bin/vicky/locks.rs index 4128955..e756734 100644 --- a/vicky/src/bin/vicky/locks.rs +++ b/vicky/src/bin/vicky/locks.rs @@ -26,15 +26,6 @@ pub async fn locks_get_poisoned_user( locks_get_poisoned(&db).await } -#[get("/poisoned", rank = 2)] -pub async fn locks_get_poisoned_machine( - db: Database, - _machine: Machine, -) -> Result>, AppError> { - locks_get_poisoned(&db).await -} - - #[get("/poisoned_detailed")] pub async fn locks_get_detailed_poisoned_user( db: Database, @@ -43,14 +34,6 @@ pub async fn locks_get_detailed_poisoned_user( locks_get_detailed_poisoned(&db).await } -#[get("/poisoned_detailed", rank = 2)] -pub async fn locks_get_detailed_poisoned_machine( - db: Database, - _machine: Machine, -) -> Result>, AppError> { - locks_get_detailed_poisoned(&db).await -} - async fn locks_get_active(db: &Database) -> Result>, AppError> { let locks: Vec = db.run(PgConnection::get_active_locks).await?; Ok(Json(locks)) @@ -64,18 +47,10 @@ pub async fn locks_get_active_user( locks_get_active(&db).await } -#[get("/active", rank = 2)] -pub async fn locks_get_active_machine( - db: Database, - _machine: Machine, -) -> Result>, AppError> { - locks_get_active(&db).await -} - #[patch("/unlock/")] pub async fn locks_unlock( db: Database, - _user: Machine, // TODO: Should actually be user-only, but we don't have that yet + _user: User, // TODO: Should actually be user-only, but we don't have that yet lock_id: String, ) -> Result<(), AppError> { let lock_uuid = Uuid::try_parse(&lock_id)?; diff --git a/vicky/src/bin/vicky/main.rs b/vicky/src/bin/vicky/main.rs index 535d72a..b84afc9 100644 --- a/vicky/src/bin/vicky/main.rs +++ b/vicky/src/bin/vicky/main.rs @@ -18,13 +18,13 @@ use vickylib::s3::client::S3Client; use crate::events::{get_global_events, GlobalEvent}; use crate::locks::{ - locks_get_active_machine, locks_get_active_user, locks_get_detailed_poisoned_machine, - locks_get_detailed_poisoned_user, locks_get_poisoned_machine, locks_get_poisoned_user, + locks_get_active_user, + locks_get_detailed_poisoned_user, locks_get_poisoned_user, locks_unlock, }; use crate::tasks::{ - tasks_add, tasks_claim, tasks_finish, tasks_get_logs, tasks_get_machine, tasks_get_user, - tasks_put_logs, tasks_specific_get_machine, tasks_specific_get_user, + tasks_add, tasks_claim, tasks_finish, tasks_get_logs, tasks_get_user, + tasks_put_logs, tasks_specific_get_user, }; use crate::user::get_user; use crate::webconfig::get_web_config; @@ -66,8 +66,6 @@ pub struct WebConfig { #[derive(Deserialize)] pub struct Config { - machines: Vec, - s3_config: S3Config, oidc_config: OIDCConfig, @@ -224,9 +222,7 @@ async fn main() -> anyhow::Result<()> { .mount( "/api/v1/tasks", routes![ - tasks_get_machine, tasks_get_user, - tasks_specific_get_machine, tasks_specific_get_user, tasks_claim, tasks_finish, @@ -239,11 +235,8 @@ async fn main() -> anyhow::Result<()> { "/api/v1/locks", routes![ locks_get_poisoned_user, - locks_get_poisoned_machine, locks_get_detailed_poisoned_user, - locks_get_detailed_poisoned_machine, locks_get_active_user, - locks_get_active_machine, locks_unlock ], ) diff --git a/vicky/src/bin/vicky/tasks.rs b/vicky/src/bin/vicky/tasks.rs index 5a841ae..ec071cf 100644 --- a/vicky/src/bin/vicky/tasks.rs +++ b/vicky/src/bin/vicky/tasks.rs @@ -14,7 +14,7 @@ use vickylib::{ use vickylib::database::entities::lock::db_impl::LockDatabase; use crate::{ - auth::{Machine, User}, + auth::{User}, errors::AppError, events::GlobalEvent, }; @@ -54,15 +54,6 @@ pub async fn tasks_get_user(db: Database, _user: User) -> Result> Ok(Json(tasks)) } -#[get("/", rank = 2)] -pub async fn tasks_get_machine( - db: Database, - _machine: Machine, -) -> Result>, VickyError> { - let tasks: Vec = db.run(|conn| conn.get_all_tasks()).await?; - Ok(Json(tasks)) -} - async fn tasks_specific_get(id: &str, db: &Database) -> Result>, VickyError> { let task_uuid = Uuid::parse_str(id).unwrap(); let tasks: Option = db.run(move |conn| conn.get_task(task_uuid)).await?; @@ -78,15 +69,6 @@ pub async fn tasks_specific_get_user( tasks_specific_get(&id, &db).await } -#[get("/", rank = 2)] -pub async fn tasks_specific_get_machine( - id: String, - db: Database, - _machine: Machine, -) -> Result>, VickyError> { - tasks_specific_get(&id, &db).await -} - #[get("//logs")] pub async fn tasks_get_logs<'a>( id: String, @@ -156,7 +138,7 @@ pub async fn tasks_put_logs( id: String, db: Database, logs: Json, - _machine: Machine, + _user: User, log_drain: &State<&LogDrain>, ) -> Result, AppError> { let task_uuid = Uuid::parse_str(&id)?; @@ -179,7 +161,7 @@ pub async fn tasks_claim( db: Database, features: Json, global_events: &State>, - _machine: Machine, + _user: User, ) -> Result>, AppError> { let tasks = db.run(|conn| conn.get_all_tasks()).await?; let poisoned_locks = db.run(|conn| conn.get_poisoned_locks()).await?; @@ -209,7 +191,7 @@ pub async fn tasks_finish( finish: Json, db: Database, global_events: &State>, - _machine: Machine, + _user: User, log_drain: &State<&LogDrain>, ) -> Result, AppError> { let task_uuid = Uuid::parse_str(&id)?; @@ -249,7 +231,7 @@ pub async fn tasks_add( task: Json, db: Database, global_events: &State>, - _machine: Machine, + _user: User, ) -> Result, AppError> { let task_uuid = Uuid::new_v4(); From 8c56be1549049403ccfa651cadcfac0c49d49688 Mon Sep 17 00:00:00 2001 From: Johann Wagner Date: Sun, 16 Jun 2024 23:54:14 +0200 Subject: [PATCH 02/21] feat: Started implementing OIDC in vickyctl --- Cargo.lock | 53 ++++++++++++++++++ vickyctl/Cargo.toml | 4 ++ vickyctl/src/account.rs | 89 +++++++++++++++++++++++++++++++ vickyctl/src/cli.rs | 24 ++++++--- vickyctl/src/error.rs | 10 ++++ vickyctl/src/http_client.rs | 24 +++++++-- vickyctl/src/locks/http.rs | 19 +++---- vickyctl/src/main.rs | 86 ++++++++++++++++++++++++++--- vickyctl/src/tasks.rs | 26 ++++----- vickyctl/src/tui/lock_resolver.rs | 36 ++++++++----- 10 files changed, 320 insertions(+), 51 deletions(-) create mode 100644 vickyctl/src/account.rs diff --git a/Cargo.lock b/Cargo.lock index 8cca400..b0fd90d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1138,6 +1138,27 @@ dependencies = [ "subtle", ] +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + [[package]] name = "displaydoc" version = "0.2.4" @@ -1390,6 +1411,7 @@ dependencies = [ "atomic 0.6.0", "pear", "serde", + "serde_json", "toml", "uncased", "version_check", @@ -2275,6 +2297,16 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.5.0", + "libc", +] + [[package]] name = "linux-raw-sys" version = "0.4.14" @@ -2654,6 +2686,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "ordered-float" version = "2.10.1" @@ -3112,6 +3150,17 @@ dependencies = [ "bitflags 2.5.0", ] +[[package]] +name = "redox_users" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + [[package]] name = "ref-cast" version = "1.0.23" @@ -4594,9 +4643,13 @@ dependencies = [ name = "vickyctl" version = "0.1.0" dependencies = [ + "anyhow", "clap", "crossterm", + "dirs", + "figment", "log", + "openidconnect", "ratatui", "ratatui-widgets", "reqwest 0.12.4", diff --git a/vickyctl/Cargo.toml b/vickyctl/Cargo.toml index 42c546d..fa22350 100644 --- a/vickyctl/Cargo.toml +++ b/vickyctl/Cargo.toml @@ -15,3 +15,7 @@ which = "6.0.1" ratatui = { version = "0.26.3", features = ["serde"] } ratatui-widgets = "0.1.6" crossterm = "0.27.0" +openidconnect = "3.5.0" +anyhow = "1.0.86" +dirs = "5.0.1" +figment = { version = "0.10.19", features = ["json", "env"] } diff --git a/vickyctl/src/account.rs b/vickyctl/src/account.rs new file mode 100644 index 0000000..f92ccbe --- /dev/null +++ b/vickyctl/src/account.rs @@ -0,0 +1,89 @@ +use openidconnect::{ErrorResponse, ClientId, IssuerUrl, core::{CoreProviderMetadata, CoreClient, CoreDeviceAuthorizationResponse, CoreAuthDisplay, CoreClientAuthMethod, CoreClaimName, CoreClaimType, CoreGrantType, CoreJweContentEncryptionAlgorithm, CoreJweKeyManagementAlgorithm, CoreJsonWebKey, CoreResponseMode, CoreResponseType, CoreSubjectIdentifierType, CoreJwsSigningAlgorithm, CoreJsonWebKeyType, CoreJsonWebKeyUse}, Scope, reqwest::{http_client}, AdditionalProviderMetadata, ProviderMetadata, DeviceAuthorizationUrl, AuthType, OAuth2TokenResponse}; +use serde::{Deserialize, Serialize}; + +use crate::{cli::AppContext, error::Error, FileConfig, AuthState}; + + +// Taken from https://github.com/ramosbugs/openidconnect-rs/blob/support/3.x/examples/okta_device_grant.rs +#[derive(Clone, Debug, Deserialize, Serialize)] +struct DeviceEndpointProviderMetadata { + device_authorization_endpoint: DeviceAuthorizationUrl, +} +impl AdditionalProviderMetadata for DeviceEndpointProviderMetadata {} +type DeviceProviderMetadata = ProviderMetadata< + DeviceEndpointProviderMetadata, + CoreAuthDisplay, + CoreClientAuthMethod, + CoreClaimName, + CoreClaimType, + CoreGrantType, + CoreJweContentEncryptionAlgorithm, + CoreJweKeyManagementAlgorithm, + CoreJwsSigningAlgorithm, + CoreJsonWebKeyType, + CoreJsonWebKeyUse, + CoreJsonWebKey, + CoreResponseMode, + CoreResponseType, + CoreSubjectIdentifierType, +>; + + +pub fn show(auth_state: &AuthState) -> Result<(), anyhow::Error> { + print!("{:?}", auth_state.clone()); + Ok(()) +} + +pub fn login(ctx: &AppContext, vicky_url_str: String, issuer_url_str: String, client_id_str: String) -> Result<(), anyhow::Error> { + + let client_id = ClientId::new(client_id_str.clone().to_string()); + let issuer_url = IssuerUrl::new(issuer_url_str.clone().to_string())?; + + + let provider_metadata = DeviceProviderMetadata::discover(&issuer_url, http_client)?; + + let device_authorization_endpoint = provider_metadata + .additional_metadata() + .device_authorization_endpoint + .clone(); + + let client = CoreClient::from_provider_metadata( + provider_metadata, + client_id, + None, + ) + .set_device_authorization_uri(device_authorization_endpoint) + .set_auth_type(AuthType::RequestBody); + + let details: CoreDeviceAuthorizationResponse = client + .exchange_device_code()? + .add_scope(Scope::new("profile".to_string())) + .request(http_client)?; + + + println!("Fetching device code..."); + dbg!(&details); + + // Display the URL and user-code. + println!( + "Open this URL in your browser:\n{}\nand enter the code: {}", + details.verification_uri_complete().unwrap().secret(), + details.user_code().secret() + ); + + // Now poll for the token + let token = client + .exchange_device_access_token(&details) + .request(http_client, std::thread::sleep, None)?; + + let account_cfg = FileConfig { + vicky_url: vicky_url_str, + client_id: client_id_str, + issuer_url: issuer_url_str, + refresh_token: token.refresh_token().unwrap().secret().to_string(), + }; + account_cfg.save()?; + + Ok(()) +} + diff --git a/vickyctl/src/cli.rs b/vickyctl/src/cli.rs index c1d0d3f..07507ff 100644 --- a/vickyctl/src/cli.rs +++ b/vickyctl/src/cli.rs @@ -4,12 +4,6 @@ use uuid::Uuid; // TODO: Add abouts to arguments #[derive(Parser, Debug, Clone)] pub struct AppContext { - #[clap(env)] - pub vicky_url: String, - - #[clap(env)] - pub vicky_token: String, - #[clap(long)] pub humanize: bool, } @@ -38,6 +32,13 @@ pub enum TaskCommands { Finish { id: Uuid, status: String }, } + +#[derive(Subcommand, Debug)] +pub enum AccountCommands { + Show, + Login {vicky_url: String, issuer_url: String, client_id: String}, +} + #[derive(Args, Debug)] #[command(version, about = "Manage tasks on the vicky delegation server", long_about = None)] pub struct TaskArgs { @@ -55,6 +56,16 @@ pub struct TasksArgs { pub ctx: AppContext, } +#[derive(Args, Debug)] +#[command(version, about = "Show all accounts", long_about = None)] +pub struct AccountArgs { + #[command(subcommand)] + pub commands: AccountCommands, + + #[command(flatten)] + pub ctx: AppContext, +} + #[derive(Args, Debug)] #[command(version, about = "Show all poisoned locks vicky is managing", long_about = None)] pub struct LocksArgs { @@ -80,6 +91,7 @@ pub struct ResolveArgs { #[derive(Parser, Debug)] #[command(version, about, long_about = None)] pub enum Cli { + Account(AccountArgs), Task(TaskArgs), Tasks(TasksArgs), Locks(LocksArgs), diff --git a/vickyctl/src/error.rs b/vickyctl/src/error.rs index 8475f16..82e1889 100644 --- a/vickyctl/src/error.rs +++ b/vickyctl/src/error.rs @@ -9,8 +9,10 @@ pub enum Error { ReqwestDetailed(reqwest::Error, String), Io(std::io::Error), Json(serde_json::Error), + Unauthenticated(), #[allow(dead_code)] Custom(&'static str), + Anyhow(anyhow::Error), } impl From for Error { @@ -37,6 +39,12 @@ impl From for Error { } } +impl From for Error { + fn from(e: anyhow::Error) -> Self { + Error::Anyhow(e) + } +} + impl Display for Error { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { @@ -55,6 +63,8 @@ impl Display for Error { Error::Io(e) => write!(f, "Filesystem Error: {}", e), Error::Json(e) => write!(f, "Parser Error: {}", e), Error::Custom(ref str) => write!(f, "Custom Error: {}", str), + Error::Anyhow(ref str) => write!(f, "Unknown Error: {}", str), + Error::Unauthenticated() => write!(f, "Unauthenticated"), Error::ReqwestDetailed(e, ref detail) => { write!(f, "{}", format_http_msg(e.status(), detail)) } diff --git a/vickyctl/src/http_client.rs b/vickyctl/src/http_client.rs index e5bf012..037b199 100644 --- a/vickyctl/src/http_client.rs +++ b/vickyctl/src/http_client.rs @@ -1,18 +1,34 @@ +use crate::AuthState; use crate::error::Error; use reqwest::blocking::Client; use reqwest::header::{HeaderMap, AUTHORIZATION}; use reqwest::StatusCode; use yansi::Paint; -use crate::cli::AppContext; -pub fn prepare_client(ctx: &AppContext) -> Result { +pub fn prepare_client(auth_state: &AuthState) -> Result<(Client, String), Error> { + + let base_url: String; + let auth_token: String = "".to_owned(); + + match auth_state { + AuthState::EnvironmentAuthenticated(envConfig) => { + base_url = envConfig.url.clone(); + }, + AuthState::FileAuthenticated(fileCfg) => { + base_url = fileCfg.vicky_url.clone(); + }, + AuthState::Unauthenticated => { + return Err(Error::Unauthenticated()) + }, + } + let mut default_headers = HeaderMap::new(); - default_headers.insert(AUTHORIZATION, ctx.vicky_token.parse().unwrap()); + default_headers.insert(AUTHORIZATION, auth_token.parse().unwrap()); let client = Client::builder() .default_headers(default_headers) .user_agent(format!("vickyctl/{}", env!("CARGO_PKG_VERSION"))) .build()?; - Ok(client) + Ok((client, base_url)) } pub fn print_http(status: Option, msg: &str) { diff --git a/vickyctl/src/locks/http.rs b/vickyctl/src/locks/http.rs index 4762153..82ee599 100644 --- a/vickyctl/src/locks/http.rs +++ b/vickyctl/src/locks/http.rs @@ -1,4 +1,6 @@ -use crate::cli::AppContext; +use reqwest::blocking::Client; + +use crate::AuthState; use crate::error::Error; use crate::http_client::prepare_client; use crate::locks::types::{LockType, PoisonedLock}; @@ -14,15 +16,15 @@ pub fn get_locks_endpoint(lock_type: LockType, detailed: bool) -> &'static str { } pub fn fetch_locks_raw( - ctx: &AppContext, + client: &Client, + vicky_url: String, lock_type: LockType, detailed: bool, ) -> Result { - let client = prepare_client(ctx)?; let request = client .get(format!( "{}/{}", - ctx.vicky_url, + vicky_url, get_locks_endpoint(lock_type, detailed) )) .build()?; @@ -32,18 +34,17 @@ pub fn fetch_locks_raw( Ok(locks) } -pub fn fetch_detailed_poisoned_locks(ctx: &AppContext) -> Result, Error> { - let raw_locks = fetch_locks_raw(ctx, LockType::Poisoned, true)?; +pub fn fetch_detailed_poisoned_locks(client: &Client, vicky_url: String) -> Result, Error> { + let raw_locks = fetch_locks_raw(client, vicky_url, LockType::Poisoned, true)?; let locks: Vec = serde_json::from_str(&raw_locks)?; Ok(locks) } -pub fn unlock_lock(ctx: &AppContext, lock_to_clear: &PoisonedLock) -> Result<(), Error> { - let client = prepare_client(ctx)?; +pub fn unlock_lock(client: &Client, vicky_url: String, lock_to_clear: &PoisonedLock) -> Result<(), Error> { let request = client .patch(format!( "{}/api/v1/locks/unlock/{}", - ctx.vicky_url, + vicky_url, lock_to_clear.id() )) .build()?; diff --git a/vickyctl/src/main.rs b/vickyctl/src/main.rs index d8379e6..1cd8937 100644 --- a/vickyctl/src/main.rs +++ b/vickyctl/src/main.rs @@ -5,23 +5,97 @@ mod humanize; mod locks; mod tasks; mod tui; +mod account; + +use std::fs::{File, self}; +use std::path::PathBuf; use crate::cli::{Cli, TaskCommands}; use crate::tasks::{claim_task, create_task, finish_task}; +use account::{login, show}; use clap::Parser; +use cli::AccountCommands; +use figment::Figment; +use figment::providers::{Env, Json, Format}; +use serde::{Serialize, Deserialize}; + + +#[derive(Serialize, Deserialize, Debug)] +pub struct EnvConfig { + issuer_url: String, + url: String, + client_id: String, + client_secret: String, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct FileConfig { + issuer_url: String, + vicky_url: String, + client_id: String, + refresh_token: String, +} + +#[derive(Debug)] +pub enum AuthState { + EnvironmentAuthenticated(EnvConfig), + FileAuthenticated(FileConfig), + Unauthenticated, +} + +impl FileConfig { + fn save(&self) -> Result<(), anyhow::Error> { + let mut path:PathBuf = dirs::config_dir().unwrap(); + + path.push("vickyctl"); + fs::create_dir_all(path.clone())?; + + path.push("account.json"); + let config_file = File::create_new(path)?; + + serde_json::to_writer_pretty(config_file, self)?; + Ok(()) + } +} fn main() { let cli = Cli::parse(); + let mut account_config_path:PathBuf = dirs::config_dir().unwrap(); + account_config_path.push("vickyctl/account.json"); + + let account_config: Option = Figment::new() + .merge(Json::file(account_config_path)) + .extract().ok(); + + let env_config: Option = Figment::new() + .merge(Env::prefixed("VICKY_")) + .extract().ok(); + + + let mut auth_state = AuthState::Unauthenticated; + + if let Some(env_config) = env_config { + auth_state = AuthState::EnvironmentAuthenticated(env_config); + } else if let Some(account_config) = account_config { + auth_state = AuthState::FileAuthenticated(account_config); + } + let error: Result<_, _> = match cli { Cli::Task(task_args) => match task_args.commands { - TaskCommands::Create(task_data) => create_task(&task_data, &task_args.ctx), - TaskCommands::Claim { features } => claim_task(&features, &task_args.ctx), - TaskCommands::Finish { id, status } => finish_task(&id, &status, &task_args.ctx), + TaskCommands::Create(task_data) => create_task(&task_data, &task_args.ctx, &auth_state), + TaskCommands::Claim { features } => claim_task(&features, &task_args.ctx, &auth_state), + TaskCommands::Finish { id, status } => finish_task(&id, &status, &task_args.ctx, &auth_state), }, - Cli::Tasks(tasks_args) => tasks::show_tasks(&tasks_args), - Cli::Locks(locks_args) => tui::show_locks(&locks_args), - Cli::Resolve(resolve_args) => tui::resolve_lock(&resolve_args), + Cli::Tasks(tasks_args) => tasks::show_tasks(&tasks_args, &auth_state), + Cli::Locks(locks_args) => tui::show_locks(&locks_args, &auth_state), + Cli::Resolve(resolve_args) => tui::resolve_lock(&resolve_args, &auth_state), + + Cli::Account(account_args) => match account_args.commands { + AccountCommands::Show => show(&auth_state).map_err(crate::error::Error::from), + AccountCommands::Login{ vicky_url, client_id, issuer_url} => login(&account_args.ctx, vicky_url, issuer_url, client_id).map_err(crate::error::Error::from) + } + }; match error { diff --git a/vickyctl/src/tasks.rs b/vickyctl/src/tasks.rs index 1406ca3..00f3e57 100644 --- a/vickyctl/src/tasks.rs +++ b/vickyctl/src/tasks.rs @@ -7,7 +7,7 @@ use yansi::Paint; use crate::error::Error; use crate::http_client::{prepare_client, print_http}; -use crate::humanize; +use crate::{humanize, AuthState}; #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(tag = "result")] @@ -44,14 +44,14 @@ pub struct Task { pub features: Vec, } -pub fn show_tasks(tasks_args: &TasksArgs) -> Result<(), Error> { +pub fn show_tasks(tasks_args: &TasksArgs, auth_state: &AuthState) -> Result<(), Error> { if tasks_args.ctx.humanize { humanize::ensure_jless("tasks")?; } - let client = prepare_client(&tasks_args.ctx)?; + let (client, vicky_url) = prepare_client(auth_state)?; let request = client - .get(format!("{}/{}", tasks_args.ctx.vicky_url, "api/v1/tasks")) + .get(format!("{}/{}", vicky_url, "api/v1/tasks")) .build()?; let response = client.execute(request)?.error_for_status()?; @@ -97,10 +97,10 @@ struct RoTaskCreate { status: RoTaskStatus, } -pub fn create_task(task_data: &TaskData, ctx: &AppContext) -> Result<(), Error> { - let client = prepare_client(ctx)?; +pub fn create_task(task_data: &TaskData, ctx: &AppContext, auth_state: &AuthState) -> Result<(), Error> { + let (client, vicky_url) = prepare_client(auth_state)?; let request = client - .post(format!("{}/{}", ctx.vicky_url, "api/v1/tasks")) + .post(format!("{}/{}", vicky_url, "api/v1/tasks")) .body(task_data.to_json().to_string()) .build()?; @@ -127,13 +127,13 @@ pub fn create_task(task_data: &TaskData, ctx: &AppContext) -> Result<(), Error> Ok(()) } -pub fn claim_task(features: &[String], ctx: &AppContext) -> Result<(), Error> { - let client = prepare_client(ctx)?; +pub fn claim_task(features: &[String], ctx: &AppContext, auth_state: &AuthState) -> Result<(), Error> { + let (client, vicky_url) = prepare_client(auth_state)?; let data: serde_json::Value = json!({ "features": features }); let request = client - .post(format!("{}/{}", ctx.vicky_url, "api/v1/tasks/claim")) + .post(format!("{}/{}", vicky_url, "api/v1/tasks/claim")) .body(data.to_string()) .build()?; @@ -157,15 +157,15 @@ pub fn claim_task(features: &[String], ctx: &AppContext) -> Result<(), Error> { Ok(()) } -pub fn finish_task(id: &Uuid, status: &String, ctx: &AppContext) -> Result<(), Error> { - let client = prepare_client(ctx)?; +pub fn finish_task(id: &Uuid, status: &String, ctx: &AppContext, auth_state: &AuthState) -> Result<(), Error> { + let (client, vicky_url) = prepare_client(auth_state)?; let data = json!({ "result": status }); let request = client .post(format!( "{}/{}/{}/{}", - ctx.vicky_url, "api/v1/tasks", id, "finish" + vicky_url, "api/v1/tasks", id, "finish" )) .body(data.to_string()) .build()?; diff --git a/vickyctl/src/tui/lock_resolver.rs b/vickyctl/src/tui/lock_resolver.rs index 69dc080..bf54786 100644 --- a/vickyctl/src/tui/lock_resolver.rs +++ b/vickyctl/src/tui/lock_resolver.rs @@ -1,6 +1,7 @@ use crate::cli::{LocksArgs, ResolveArgs}; use crate::error::Error; -use crate::humanize; +use crate::http_client::prepare_client; +use crate::{humanize, AuthState}; use crossterm::event::{Event, KeyCode, KeyEvent}; use crossterm::terminal::{ disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen, @@ -8,6 +9,7 @@ use crossterm::terminal::{ use crossterm::{event, execute}; use ratatui::prelude::*; use ratatui::widgets::{Block, Borders, HighlightSpacing, Row, Table, TableState}; +use reqwest::blocking::Client; use std::io; use crate::locks::http::{fetch_detailed_poisoned_locks, fetch_locks_raw, unlock_lock}; use crate::locks::types::{LockType, PoisonedLock}; @@ -24,7 +26,7 @@ impl<'a> From<&'a PoisonedLock> for Row<'a> { } } -pub fn show_locks(locks_args: &LocksArgs) -> Result<(), Error> { +pub fn show_locks(locks_args: &LocksArgs, auth_state: &AuthState) -> Result<(), Error> { if locks_args.ctx.humanize { humanize::ensure_jless("lock")?; } @@ -34,14 +36,18 @@ pub fn show_locks(locks_args: &LocksArgs) -> Result<(), Error> { )); } - let locks_json = fetch_locks_raw(&locks_args.ctx, LockType::from(locks_args), false)?; + let (client, vicky_url) = prepare_client(auth_state)?; + + let locks_json = fetch_locks_raw(&client, vicky_url, LockType::from(locks_args), false)?; humanize::handle_user_response(&locks_args.ctx, &locks_json)?; Ok(()) } -pub fn resolve_lock(resolve_args: &ResolveArgs) -> Result<(), Error> { - let mut locks = fetch_detailed_poisoned_locks(&resolve_args.ctx)?; +pub fn resolve_lock(resolve_args: &ResolveArgs, auth_state: &AuthState) -> Result<(), Error> { + let (client, vicky_url) = prepare_client(auth_state)?; + + let mut locks = fetch_detailed_poisoned_locks(&client, vicky_url.clone())?; enable_raw_mode()?; let mut stdout = io::stdout(); @@ -61,8 +67,9 @@ pub fn resolve_lock(resolve_args: &ResolveArgs) -> Result<(), Error> { locks.len(), &mut selected_task, &mut selected_button, - resolve_args, &mut locks, + &client, + vicky_url.clone(), )?; terminal.draw(|f| ui(f, &locks, &mut state, &selected_task, &mut selected_button))?; } @@ -74,13 +81,14 @@ pub fn resolve_lock(resolve_args: &ResolveArgs) -> Result<(), Error> { } fn unlock_and_refresh( - resolve_args: &ResolveArgs, + client: &Client, + vicky_url: String, locks: &mut Vec, selected_task: &mut Option, ) -> Result<(), Error> { if let Some(task_idx) = selected_task { - unlock_lock(&resolve_args.ctx, &locks[*task_idx])?; - *locks = fetch_detailed_poisoned_locks(&resolve_args.ctx)?; + unlock_lock(&client, vicky_url.clone(), &locks[*task_idx])?; + *locks = fetch_detailed_poisoned_locks(&client, vicky_url)?; *selected_task = None; } Ok(()) @@ -90,7 +98,8 @@ fn handle_popup( selected_task: &mut Option, selected_button: &mut bool, key: &KeyEvent, - args: &ResolveArgs, + client: &Client, + vicky_url: String, locks: &mut Vec, ) -> Result<(), Error> { if key.code == KeyCode::Left || key.code == KeyCode::Char('y') { @@ -100,7 +109,7 @@ fn handle_popup( } if key.code == KeyCode::Char('y') || (key.code == KeyCode::Enter && *selected_button) { - unlock_and_refresh(args, locks, selected_task)?; + unlock_and_refresh(client, vicky_url, locks, selected_task)?; } else if key.code == KeyCode::Char('n') || (key.code == KeyCode::Enter && !*selected_button) { *selected_task = None; } @@ -113,8 +122,9 @@ fn handle_events( lock_amount: usize, selected_task: &mut Option, selected_button: &mut bool, - args: &ResolveArgs, locks: &mut Vec, + client: &Client, + vicky_url: String, ) -> Result { if !event::poll(std::time::Duration::from_millis(50))? { return Ok(false); @@ -135,7 +145,7 @@ fn handle_events( match selected_task { None => handle_task_list(state, lock_amount, selected_task, &key), - Some(_) => handle_popup(selected_task, selected_button, &key, args, locks)?, + Some(_) => handle_popup(selected_task, selected_button, &key, client, vicky_url, locks)?, } } From de752f2eb93c0355fd766166818cca91fdf516c8 Mon Sep 17 00:00:00 2001 From: Johann Wagner Date: Mon, 17 Jun 2024 08:59:26 +0200 Subject: [PATCH 03/21] fix: clippy --- vicky/src/bin/vicky/auth.rs | 3 --- vicky/src/bin/vicky/locks.rs | 2 +- vickyctl/src/account.rs | 8 ++++---- vickyctl/src/http_client.rs | 8 ++++---- vickyctl/src/locks/http.rs | 2 -- vickyctl/src/main.rs | 4 ++-- vickyctl/src/tui/lock_resolver.rs | 4 ++-- 7 files changed, 13 insertions(+), 18 deletions(-) diff --git a/vicky/src/bin/vicky/auth.rs b/vicky/src/bin/vicky/auth.rs index 187cb80..4240dea 100644 --- a/vicky/src/bin/vicky/auth.rs +++ b/vicky/src/bin/vicky/auth.rs @@ -33,7 +33,6 @@ impl FromStr for Role { } } -#[allow(dead_code)] #[derive(Deserialize)] pub struct User { pub id: Uuid, @@ -41,8 +40,6 @@ pub struct User { pub role: Role, } -pub struct Machine {} - async fn extract_user_from_token(jwks_verifier: &State, db: &Database, oidc_config: &OIDCConfigResolved, token: &str) -> Result { let jwt = jwks_verifier.verify::>(token).await?; diff --git a/vicky/src/bin/vicky/locks.rs b/vicky/src/bin/vicky/locks.rs index e756734..4f1fbe1 100644 --- a/vicky/src/bin/vicky/locks.rs +++ b/vicky/src/bin/vicky/locks.rs @@ -5,7 +5,7 @@ use uuid::Uuid; use vickylib::database::entities::{Database, Lock}; use vickylib::database::entities::lock::db_impl::LockDatabase; use vickylib::database::entities::lock::PoisonedLock; -use crate::auth::{Machine, User}; +use crate::auth::{User}; use crate::errors::AppError; async fn locks_get_poisoned(db: &Database) -> Result>, AppError> { diff --git a/vickyctl/src/account.rs b/vickyctl/src/account.rs index f92ccbe..468044a 100644 --- a/vickyctl/src/account.rs +++ b/vickyctl/src/account.rs @@ -1,7 +1,7 @@ -use openidconnect::{ErrorResponse, ClientId, IssuerUrl, core::{CoreProviderMetadata, CoreClient, CoreDeviceAuthorizationResponse, CoreAuthDisplay, CoreClientAuthMethod, CoreClaimName, CoreClaimType, CoreGrantType, CoreJweContentEncryptionAlgorithm, CoreJweKeyManagementAlgorithm, CoreJsonWebKey, CoreResponseMode, CoreResponseType, CoreSubjectIdentifierType, CoreJwsSigningAlgorithm, CoreJsonWebKeyType, CoreJsonWebKeyUse}, Scope, reqwest::{http_client}, AdditionalProviderMetadata, ProviderMetadata, DeviceAuthorizationUrl, AuthType, OAuth2TokenResponse}; +use openidconnect::{ClientId, IssuerUrl, core::{CoreClient, CoreDeviceAuthorizationResponse, CoreAuthDisplay, CoreClientAuthMethod, CoreClaimName, CoreClaimType, CoreGrantType, CoreJweContentEncryptionAlgorithm, CoreJweKeyManagementAlgorithm, CoreJsonWebKey, CoreResponseMode, CoreResponseType, CoreSubjectIdentifierType, CoreJwsSigningAlgorithm, CoreJsonWebKeyType, CoreJsonWebKeyUse}, Scope, reqwest::{http_client}, AdditionalProviderMetadata, ProviderMetadata, DeviceAuthorizationUrl, AuthType, OAuth2TokenResponse}; use serde::{Deserialize, Serialize}; -use crate::{cli::AppContext, error::Error, FileConfig, AuthState}; +use crate::{FileConfig, AuthState}; // Taken from https://github.com/ramosbugs/openidconnect-rs/blob/support/3.x/examples/okta_device_grant.rs @@ -30,11 +30,11 @@ type DeviceProviderMetadata = ProviderMetadata< pub fn show(auth_state: &AuthState) -> Result<(), anyhow::Error> { - print!("{:?}", auth_state.clone()); + print!("{:?}", auth_state); Ok(()) } -pub fn login(ctx: &AppContext, vicky_url_str: String, issuer_url_str: String, client_id_str: String) -> Result<(), anyhow::Error> { +pub fn login(vicky_url_str: String, issuer_url_str: String, client_id_str: String) -> Result<(), anyhow::Error> { let client_id = ClientId::new(client_id_str.clone().to_string()); let issuer_url = IssuerUrl::new(issuer_url_str.clone().to_string())?; diff --git a/vickyctl/src/http_client.rs b/vickyctl/src/http_client.rs index 037b199..d005ba0 100644 --- a/vickyctl/src/http_client.rs +++ b/vickyctl/src/http_client.rs @@ -11,11 +11,11 @@ pub fn prepare_client(auth_state: &AuthState) -> Result<(Client, String), Error> let auth_token: String = "".to_owned(); match auth_state { - AuthState::EnvironmentAuthenticated(envConfig) => { - base_url = envConfig.url.clone(); + AuthState::EnvironmentAuthenticated(env_config) => { + base_url = env_config.url.clone(); }, - AuthState::FileAuthenticated(fileCfg) => { - base_url = fileCfg.vicky_url.clone(); + AuthState::FileAuthenticated(file_config) => { + base_url = file_config.vicky_url.clone(); }, AuthState::Unauthenticated => { return Err(Error::Unauthenticated()) diff --git a/vickyctl/src/locks/http.rs b/vickyctl/src/locks/http.rs index 82ee599..68ec32f 100644 --- a/vickyctl/src/locks/http.rs +++ b/vickyctl/src/locks/http.rs @@ -1,8 +1,6 @@ use reqwest::blocking::Client; -use crate::AuthState; use crate::error::Error; -use crate::http_client::prepare_client; use crate::locks::types::{LockType, PoisonedLock}; pub fn get_locks_endpoint(lock_type: LockType, detailed: bool) -> &'static str { diff --git a/vickyctl/src/main.rs b/vickyctl/src/main.rs index 1cd8937..b45fe80 100644 --- a/vickyctl/src/main.rs +++ b/vickyctl/src/main.rs @@ -89,11 +89,11 @@ fn main() { }, Cli::Tasks(tasks_args) => tasks::show_tasks(&tasks_args, &auth_state), Cli::Locks(locks_args) => tui::show_locks(&locks_args, &auth_state), - Cli::Resolve(resolve_args) => tui::resolve_lock(&resolve_args, &auth_state), + Cli::Resolve(_) => tui::resolve_lock(&auth_state), Cli::Account(account_args) => match account_args.commands { AccountCommands::Show => show(&auth_state).map_err(crate::error::Error::from), - AccountCommands::Login{ vicky_url, client_id, issuer_url} => login(&account_args.ctx, vicky_url, issuer_url, client_id).map_err(crate::error::Error::from) + AccountCommands::Login{ vicky_url, client_id, issuer_url} => login( vicky_url, issuer_url, client_id).map_err(crate::error::Error::from) } }; diff --git a/vickyctl/src/tui/lock_resolver.rs b/vickyctl/src/tui/lock_resolver.rs index bf54786..cb3bf8b 100644 --- a/vickyctl/src/tui/lock_resolver.rs +++ b/vickyctl/src/tui/lock_resolver.rs @@ -1,4 +1,4 @@ -use crate::cli::{LocksArgs, ResolveArgs}; +use crate::cli::{LocksArgs}; use crate::error::Error; use crate::http_client::prepare_client; use crate::{humanize, AuthState}; @@ -44,7 +44,7 @@ pub fn show_locks(locks_args: &LocksArgs, auth_state: &AuthState) -> Result<(), Ok(()) } -pub fn resolve_lock(resolve_args: &ResolveArgs, auth_state: &AuthState) -> Result<(), Error> { +pub fn resolve_lock(auth_state: &AuthState) -> Result<(), Error> { let (client, vicky_url) = prepare_client(auth_state)?; let mut locks = fetch_detailed_poisoned_locks(&client, vicky_url.clone())?; From c3fe46cf566d0cc2aaa895446c92c74246fdc528 Mon Sep 17 00:00:00 2001 From: Kek5chen Date: Thu, 20 Jun 2024 10:49:13 +0200 Subject: [PATCH 04/21] fix: better error --- Cargo.lock | 1 + fairy/Cargo.toml | 1 + fairy/src/api.rs | 7 ++++--- fairy/src/error.rs | 7 +++++++ fairy/src/main.rs | 4 ++-- 5 files changed, 15 insertions(+), 5 deletions(-) create mode 100644 fairy/src/error.rs diff --git a/Cargo.lock b/Cargo.lock index b0fd90d..236c059 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1364,6 +1364,7 @@ dependencies = [ "serde", "serde_json", "serde_yaml", + "thiserror", "tokio", "tokio-util", "uuid", diff --git a/fairy/Cargo.toml b/fairy/Cargo.toml index b5f9fd5..7a05e37 100644 --- a/fairy/Cargo.toml +++ b/fairy/Cargo.toml @@ -22,6 +22,7 @@ which = "6.0.1" openidconnect = "3.5.0" reqwest = { version="0.12.4", features = ["json"]} chrono = "0.4.38" +thiserror = "1.0.61" [features] nixless-test-mode = [] \ No newline at end of file diff --git a/fairy/src/api.rs b/fairy/src/api.rs index 366ae54..4b97803 100644 --- a/fairy/src/api.rs +++ b/fairy/src/api.rs @@ -10,8 +10,8 @@ use reqwest::{self, Method, RequestBuilder}; use openidconnect::reqwest::async_http_client; - use crate::AppConfig; +use crate::error::FairyError; #[derive(Debug)] pub enum HttpClientState { @@ -37,7 +37,7 @@ impl HttpClient { } } - async fn renew_access_token(&mut self) -> anyhow::Result { + pub async fn renew_access_token(&mut self) -> anyhow::Result { let client_id = ClientId::new(self.app_config.oidc_config.client_id.clone()); let client_secret = ClientSecret::new(self.app_config.oidc_config.client_secret.clone()); let issuer_url = IssuerUrl::new(self.app_config.oidc_config.issuer_url.clone())?; @@ -55,7 +55,8 @@ impl HttpClient { .exchange_client_credentials() .add_scope(Scope::new("openid".to_string())); - let ccres = ccreq.request_async(async_http_client).await?; + let ccres = ccreq.request_async(async_http_client).await + .map_err(|_| FairyError::Unauthorized)?; let access_token = ccres.access_token().secret(); let expires_at = Utc::now() + ccres.expires_in().unwrap() - Duration::seconds(5); diff --git a/fairy/src/error.rs b/fairy/src/error.rs new file mode 100644 index 0000000..689fa89 --- /dev/null +++ b/fairy/src/error.rs @@ -0,0 +1,7 @@ +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum FairyError { + #[error("Could not authenticate against OIDC provider")] + Unauthorized +} \ No newline at end of file diff --git a/fairy/src/main.rs b/fairy/src/main.rs index a63f777..9aac6df 100644 --- a/fairy/src/main.rs +++ b/fairy/src/main.rs @@ -4,9 +4,7 @@ use std::sync::Arc; use anyhow::anyhow; use api::HttpClient; use futures_util::{Sink, StreamExt, TryStreamExt}; -use hyper::{Body, Client, Method, Request}; use serde::{Deserialize, Serialize}; -use serde::de::DeserializeOwned; use tokio::process::Command; use tokio_util::codec::{FramedRead, LinesCodec}; use uuid::Uuid; @@ -17,6 +15,7 @@ use rocket::figment::providers::{Env, Format, Toml}; use rocket::figment::{Figment, Profile}; mod api; +pub mod error; #[derive(Deserialize, Debug)] @@ -219,6 +218,7 @@ async fn try_claim(cfg: Arc, vicky_client: &mut HttpClient) -> anyhow async fn run(cfg: AppConfig) -> anyhow::Result<()> { let cfg = Arc::new(cfg); let mut vicky_client_mgmt = HttpClient::new(cfg.clone()); + vicky_client_mgmt.renew_access_token().await?; log::info!("config valid, starting communication with vicky"); log::info!("waiting for tasks..."); From 742ed2b92ffa804d8b18bfd287decb6d34ec6e20 Mon Sep 17 00:00:00 2001 From: Kek5chen Date: Thu, 20 Jun 2024 10:50:23 +0200 Subject: [PATCH 05/21] fix: don't require new file --- vickyctl/src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vickyctl/src/main.rs b/vickyctl/src/main.rs index b45fe80..b24c969 100644 --- a/vickyctl/src/main.rs +++ b/vickyctl/src/main.rs @@ -45,13 +45,13 @@ pub enum AuthState { impl FileConfig { fn save(&self) -> Result<(), anyhow::Error> { - let mut path:PathBuf = dirs::config_dir().unwrap(); + let mut path: PathBuf = dirs::config_dir().unwrap(); path.push("vickyctl"); fs::create_dir_all(path.clone())?; path.push("account.json"); - let config_file = File::create_new(path)?; + let config_file = File::create(path)?; serde_json::to_writer_pretty(config_file, self)?; Ok(()) From 5e228644ef75cd2ad3c552419b0a5ee645213843 Mon Sep 17 00:00:00 2001 From: Kek5chen Date: Thu, 20 Jun 2024 10:50:39 +0200 Subject: [PATCH 06/21] refactor: rustfmt --- fairy/src/api.rs | 80 ++++++++++++++++++++++++++------------------ fairy/src/error.rs | 4 +-- fairy/src/main.rs | 72 +++++++++++++++++++-------------------- vickyctl/src/main.rs | 53 ++++++++++++++++------------- 4 files changed, 114 insertions(+), 95 deletions(-) diff --git a/fairy/src/api.rs b/fairy/src/api.rs index 4b97803..65aec78 100644 --- a/fairy/src/api.rs +++ b/fairy/src/api.rs @@ -1,14 +1,13 @@ use std::sync::Arc; -use chrono::{Utc, DateTime, Duration}; +use chrono::{DateTime, Duration, Utc}; use log::info; -use openidconnect::core::{CoreClient, CoreProviderMetadata}; use openidconnect::{ClientId, ClientSecret, IssuerUrl, OAuth2TokenResponse, Scope}; -use serde::de::DeserializeOwned; -use serde::{Serialize}; -use reqwest::{self, Method, RequestBuilder}; +use openidconnect::core::{CoreClient, CoreProviderMetadata}; use openidconnect::reqwest::async_http_client; - +use reqwest::{self, Method, RequestBuilder}; +use serde::de::DeserializeOwned; +use serde::Serialize; use crate::AppConfig; use crate::error::FairyError; @@ -25,7 +24,7 @@ pub enum HttpClientState { pub struct HttpClient { app_config: Arc, http_client: reqwest::Client, - client_state: HttpClientState + client_state: HttpClientState, } impl HttpClient { @@ -42,53 +41,65 @@ impl HttpClient { let client_secret = ClientSecret::new(self.app_config.oidc_config.client_secret.clone()); let issuer_url = IssuerUrl::new(self.app_config.oidc_config.issuer_url.clone())?; - info!("Using {:?} as client_id to try to authorize to {:?}..", client_id, issuer_url); - - let provider_metadata = CoreProviderMetadata::discover_async(issuer_url, &async_http_client).await?; - let client = CoreClient::from_provider_metadata( - provider_metadata, - client_id, - Some(client_secret), + info!( + "Using {:?} as client_id to try to authorize to {:?}..", + client_id, issuer_url ); + let provider_metadata = + CoreProviderMetadata::discover_async(issuer_url, &async_http_client).await?; + let client = + CoreClient::from_provider_metadata(provider_metadata, client_id, Some(client_secret)); + let ccreq = client .exchange_client_credentials() .add_scope(Scope::new("openid".to_string())); - let ccres = ccreq.request_async(async_http_client).await - .map_err(|_| FairyError::Unauthorized)?; + let ccres = ccreq + .request_async(async_http_client) + .await + .map_err(|_| FairyError::Unauthorized)?; let access_token = ccres.access_token().secret(); let expires_at = Utc::now() + ccres.expires_in().unwrap() - Duration::seconds(5); info!("Accquired access token, expiring at {:?} ..", expires_at); - self.client_state = HttpClientState::Authenticated { access_token: access_token.clone(), expires_at }; + self.client_state = HttpClientState::Authenticated { + access_token: access_token.clone(), + expires_at, + }; Ok(access_token.clone()) } - async fn create_request(&mut self, method: Method, url: U) -> anyhow::Result { - + async fn create_request( + &mut self, + method: Method, + url: U, + ) -> anyhow::Result { let now = Utc::now(); info!("client_state: {:?}", self.client_state); let access_token_to_use = match &self.client_state { - HttpClientState::Authenticated { expires_at, access_token } => { + HttpClientState::Authenticated { + expires_at, + access_token, + } => { if expires_at > &now { access_token.to_string() } else { self.renew_access_token().await? } - }, - HttpClientState::Unauthenticated => { - self.renew_access_token().await? - }, + } + HttpClientState::Unauthenticated => self.renew_access_token().await?, }; - Ok(self.http_client.request(method, url).header("Authorization", format!("Bearer {}", access_token_to_use))) - } - + Ok(self + .http_client + .request(method, url) + .header("Authorization", format!("Bearer {}", access_token_to_use))) + } pub async fn do_request( &mut self, @@ -96,18 +107,21 @@ impl HttpClient { endpoint: &str, q: &BODY, ) -> anyhow::Result { - let response = self - .create_request(method, format!("{}/{}", self.app_config.vicky_url, endpoint)).await? + .create_request( + method, + format!("{}/{}", self.app_config.vicky_url, endpoint), + ) + .await? .header("content-type", "application/json") .json(q) - .send().await?; - - + .send() + .await?; + if !response.status().is_success() { anyhow::bail!("API error: {:?}", response); } - + Ok(response.json().await?) } } diff --git a/fairy/src/error.rs b/fairy/src/error.rs index 689fa89..0b9030b 100644 --- a/fairy/src/error.rs +++ b/fairy/src/error.rs @@ -3,5 +3,5 @@ use thiserror::Error; #[derive(Error, Debug)] pub enum FairyError { #[error("Could not authenticate against OIDC provider")] - Unauthorized -} \ No newline at end of file + Unauthorized, +} diff --git a/fairy/src/main.rs b/fairy/src/main.rs index 9aac6df..8751831 100644 --- a/fairy/src/main.rs +++ b/fairy/src/main.rs @@ -2,22 +2,21 @@ use std::process::{exit, Stdio}; use std::sync::Arc; use anyhow::anyhow; -use api::HttpClient; use futures_util::{Sink, StreamExt, TryStreamExt}; +use reqwest::{self, Method}; +use rocket::figment::{Figment, Profile}; +use rocket::figment::providers::{Env, Format, Toml}; use serde::{Deserialize, Serialize}; use tokio::process::Command; use tokio_util::codec::{FramedRead, LinesCodec}; use uuid::Uuid; use which::which; -use reqwest::{self, Method}; -use rocket::figment::providers::{Env, Format, Toml}; -use rocket::figment::{Figment, Profile}; +use api::HttpClient; mod api; pub mod error; - #[derive(Deserialize, Debug)] pub struct OIDCConfig { issuer_url: String, @@ -70,8 +69,6 @@ fn main() -> anyhow::Result<()> { run(app_config) } - - #[derive(Debug, Deserialize)] pub struct FlakeRef { pub flake: String, @@ -107,14 +104,16 @@ fn log_sink( ) -> impl Sink, Error = anyhow::Error> + Send { let vicky_client_task = HttpClient::new(cfg.clone()); - futures_util::sink::unfold(vicky_client_task, move |mut http_client, lines: Vec| { - async move { - let response = http_client.do_request::<_, ()>( - Method::POST, - &format!("api/v1/tasks/{}/logs", task_id), - &serde_json::json!({ "lines": lines }), - ) - .await; + futures_util::sink::unfold( + vicky_client_task, + move |mut http_client, lines: Vec| async move { + let response = http_client + .do_request::<_, ()>( + Method::POST, + &format!("api/v1/tasks/{}/logs", task_id), + &serde_json::json!({ "lines": lines }), + ) + .await; match response { Ok(_) => { @@ -129,8 +128,8 @@ fn log_sink( Err(e) } } - } - }) + }, + ) } async fn try_run_task(cfg: Arc, task: &Task) -> anyhow::Result<()> { @@ -170,38 +169,39 @@ async fn try_run_task(cfg: Arc, task: &Task) -> anyhow::Result<()> { } async fn run_task(cfg: Arc, task: Task) { - let mut vicky_client_task = HttpClient::new(cfg.clone()); #[cfg(not(feature = "nixless-test-mode"))] let result = match try_run_task(cfg.clone(), &task).await { - Err(e) => { - log::info!("task failed: {} {} {:?}", task.id, task.display_name, e); - TaskResult::Error - } - Ok(_) => TaskResult::Success, - }; + Err(e) => { + log::info!("task failed: {} {} {:?}", task.id, task.display_name, e); + TaskResult::Error + } + Ok(_) => TaskResult::Success, + }; #[cfg(feature = "nixless-test-mode")] let result = TaskResult::Success; tokio::time::sleep(std::time::Duration::from_secs(1)).await; - let _ = vicky_client_task.do_request::<_, ()>( - Method::POST, - &format!("api/v1/tasks/{}/finish", task.id), - &serde_json::json!({ "result": result }), - ) - .await; + let _ = vicky_client_task + .do_request::<_, ()>( + Method::POST, + &format!("api/v1/tasks/{}/finish", task.id), + &serde_json::json!({ "result": result }), + ) + .await; } async fn try_claim(cfg: Arc, vicky_client: &mut HttpClient) -> anyhow::Result<()> { log::debug!("trying to claim task..."); - if let Some(task) = vicky_client.do_request::<_, Option>( - Method::POST, - "api/v1/tasks/claim", - &serde_json::json!({ "features": cfg.features }), - ) - .await? + if let Some(task) = vicky_client + .do_request::<_, Option>( + Method::POST, + "api/v1/tasks/claim", + &serde_json::json!({ "features": cfg.features }), + ) + .await? { log::info!("task claimed: {} {} 🎉", task.id, task.display_name); log::debug!("{:#?}", task); diff --git a/vickyctl/src/main.rs b/vickyctl/src/main.rs index b24c969..20716b5 100644 --- a/vickyctl/src/main.rs +++ b/vickyctl/src/main.rs @@ -1,3 +1,18 @@ +use std::fs::{self, File}; +use std::path::PathBuf; + +use clap::Parser; +use figment::Figment; +use figment::providers::{Env, Format, Json}; +use serde::{Deserialize, Serialize}; + +use account::{login, show}; +use cli::AccountCommands; + +use crate::cli::{Cli, TaskCommands}; +use crate::tasks::{claim_task, create_task, finish_task}; + +mod account; mod cli; mod error; mod http_client; @@ -5,20 +20,6 @@ mod humanize; mod locks; mod tasks; mod tui; -mod account; - -use std::fs::{File, self}; -use std::path::PathBuf; - -use crate::cli::{Cli, TaskCommands}; -use crate::tasks::{claim_task, create_task, finish_task}; -use account::{login, show}; -use clap::Parser; -use cli::AccountCommands; -use figment::Figment; -use figment::providers::{Env, Json, Format}; -use serde::{Serialize, Deserialize}; - #[derive(Serialize, Deserialize, Debug)] pub struct EnvConfig { @@ -61,18 +62,17 @@ impl FileConfig { fn main() { let cli = Cli::parse(); - let mut account_config_path:PathBuf = dirs::config_dir().unwrap(); + let mut account_config_path: PathBuf = dirs::config_dir().unwrap(); account_config_path.push("vickyctl/account.json"); let account_config: Option = Figment::new() .merge(Json::file(account_config_path)) - .extract().ok(); + .extract() + .ok(); - let env_config: Option = Figment::new() - .merge(Env::prefixed("VICKY_")) - .extract().ok(); + let env_config: Option = + Figment::new().merge(Env::prefixed("VICKY_")).extract().ok(); - let mut auth_state = AuthState::Unauthenticated; if let Some(env_config) = env_config { @@ -85,7 +85,9 @@ fn main() { Cli::Task(task_args) => match task_args.commands { TaskCommands::Create(task_data) => create_task(&task_data, &task_args.ctx, &auth_state), TaskCommands::Claim { features } => claim_task(&features, &task_args.ctx, &auth_state), - TaskCommands::Finish { id, status } => finish_task(&id, &status, &task_args.ctx, &auth_state), + TaskCommands::Finish { id, status } => { + finish_task(&id, &status, &task_args.ctx, &auth_state) + } }, Cli::Tasks(tasks_args) => tasks::show_tasks(&tasks_args, &auth_state), Cli::Locks(locks_args) => tui::show_locks(&locks_args, &auth_state), @@ -93,9 +95,12 @@ fn main() { Cli::Account(account_args) => match account_args.commands { AccountCommands::Show => show(&auth_state).map_err(crate::error::Error::from), - AccountCommands::Login{ vicky_url, client_id, issuer_url} => login( vicky_url, issuer_url, client_id).map_err(crate::error::Error::from) - } - + AccountCommands::Login { + vicky_url, + client_id, + issuer_url, + } => login(vicky_url, issuer_url, client_id).map_err(crate::error::Error::from), + }, }; match error { From 61e548bf82b0e29d4768b23ea896345c158f69b0 Mon Sep 17 00:00:00 2001 From: Kek5chen Date: Thu, 20 Jun 2024 10:56:21 +0200 Subject: [PATCH 07/21] refactor: clean up --- fairy/src/config.rs | 16 +++ fairy/src/main.rs | 207 ++---------------------------------- fairy/src/tasks/log_sink.rs | 42 ++++++++ fairy/src/tasks/mod.rs | 3 + fairy/src/tasks/runner.rs | 112 +++++++++++++++++++ fairy/src/tasks/types.rs | 33 ++++++ fairy/src/utils.rs | 12 +++ 7 files changed, 224 insertions(+), 201 deletions(-) create mode 100644 fairy/src/config.rs create mode 100644 fairy/src/tasks/log_sink.rs create mode 100644 fairy/src/tasks/mod.rs create mode 100644 fairy/src/tasks/runner.rs create mode 100644 fairy/src/tasks/types.rs create mode 100644 fairy/src/utils.rs diff --git a/fairy/src/config.rs b/fairy/src/config.rs new file mode 100644 index 0000000..bc57d40 --- /dev/null +++ b/fairy/src/config.rs @@ -0,0 +1,16 @@ +use rocket::serde::Deserialize; + +#[derive(Deserialize, Debug)] +pub struct OIDCConfig { + issuer_url: String, + client_id: String, + client_secret: String, +} + +#[derive(Deserialize, Debug)] +pub(crate) struct AppConfig { + pub(crate) vicky_url: String, + pub(crate) vicky_external_url: String, + pub(crate) features: Vec, + pub(crate) oidc_config: OIDCConfig, +} diff --git a/fairy/src/main.rs b/fairy/src/main.rs index 8751831..194e9ee 100644 --- a/fairy/src/main.rs +++ b/fairy/src/main.rs @@ -1,45 +1,13 @@ -use std::process::{exit, Stdio}; -use std::sync::Arc; - -use anyhow::anyhow; -use futures_util::{Sink, StreamExt, TryStreamExt}; -use reqwest::{self, Method}; use rocket::figment::{Figment, Profile}; use rocket::figment::providers::{Env, Format, Toml}; -use serde::{Deserialize, Serialize}; -use tokio::process::Command; -use tokio_util::codec::{FramedRead, LinesCodec}; -use uuid::Uuid; -use which::which; -use api::HttpClient; +use crate::config::AppConfig; mod api; -pub mod error; - -#[derive(Deserialize, Debug)] -pub struct OIDCConfig { - issuer_url: String, - client_id: String, - client_secret: String, -} - -#[derive(Deserialize, Debug)] -pub(crate) struct AppConfig { - pub(crate) vicky_url: String, - pub(crate) vicky_external_url: String, - pub(crate) features: Vec, - pub(crate) oidc_config: OIDCConfig, -} - -const CODE_NIX_NOT_INSTALLED: i32 = 1; - -fn ensure_nix() { - if which("nix").is_err() { - log::error!("\"nix\" binary not found. Please install nix or run on a nix-os host."); - exit(CODE_NIX_NOT_INSTALLED); - } -} +mod config; +mod error; +mod tasks; +mod utils; fn main() -> anyhow::Result<()> { env_logger::builder() @@ -66,168 +34,5 @@ fn main() -> anyhow::Result<()> { )); let app_config = rocket_config_figment.extract::()?; - run(app_config) -} - -#[derive(Debug, Deserialize)] -pub struct FlakeRef { - pub flake: String, - pub args: Vec, -} - -#[derive(Debug, Serialize, Deserialize)] -#[serde(tag = "result")] -pub enum TaskResult { - Success, - Error, -} - -#[derive(Debug, Deserialize)] -#[serde(tag = "state")] -pub enum TaskStatus { - New, - Running, - Finished(TaskResult), -} - -#[derive(Debug, Deserialize)] -pub struct Task { - pub id: Uuid, - pub display_name: String, - pub status: TaskStatus, - pub flake_ref: FlakeRef, -} - -fn log_sink( - cfg: Arc, - task_id: Uuid, -) -> impl Sink, Error = anyhow::Error> + Send { - let vicky_client_task = HttpClient::new(cfg.clone()); - - futures_util::sink::unfold( - vicky_client_task, - move |mut http_client, lines: Vec| async move { - let response = http_client - .do_request::<_, ()>( - Method::POST, - &format!("api/v1/tasks/{}/logs", task_id), - &serde_json::json!({ "lines": lines }), - ) - .await; - - match response { - Ok(_) => { - log::info!("logged {} line(s) from task", lines.len()); - Ok(http_client) - } - Err(e) => { - log::error!( - "could not log from task. {} lines were dropped", - lines.len() - ); - Err(e) - } - } - }, - ) -} - -async fn try_run_task(cfg: Arc, task: &Task) -> anyhow::Result<()> { - let mut args = vec!["run".into(), "-L".into(), task.flake_ref.flake.clone()]; - args.extend(task.flake_ref.args.clone()); - - let mut child = Command::new("nix") - .args(args) - .env("VICKY_API_URL", &cfg.vicky_external_url) - .kill_on_drop(true) - .stdin(Stdio::null()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn()?; - - let logger = log_sink(cfg.clone(), task.id); - - let lines = futures_util::stream::select( - FramedRead::new(child.stdout.take().unwrap(), LinesCodec::new()), - FramedRead::new(child.stderr.take().unwrap(), LinesCodec::new()), - ); - - lines - .ready_chunks(1024) // TODO switch to try_ready_chunks - .map(|v| v.into_iter().collect::, _>>()) - .map_err(anyhow::Error::from) - .forward(logger) - .await?; - let exit_status = child.wait().await?; - - if exit_status.success() { - log::info!("task finished: {} {} 🎉", task.id, task.display_name); - Ok(()) - } else { - Err(anyhow!("exit code {:?}", exit_status.code())) - } -} - -async fn run_task(cfg: Arc, task: Task) { - let mut vicky_client_task = HttpClient::new(cfg.clone()); - - #[cfg(not(feature = "nixless-test-mode"))] - let result = match try_run_task(cfg.clone(), &task).await { - Err(e) => { - log::info!("task failed: {} {} {:?}", task.id, task.display_name, e); - TaskResult::Error - } - Ok(_) => TaskResult::Success, - }; - - #[cfg(feature = "nixless-test-mode")] - let result = TaskResult::Success; - - tokio::time::sleep(std::time::Duration::from_secs(1)).await; - let _ = vicky_client_task - .do_request::<_, ()>( - Method::POST, - &format!("api/v1/tasks/{}/finish", task.id), - &serde_json::json!({ "result": result }), - ) - .await; -} - -async fn try_claim(cfg: Arc, vicky_client: &mut HttpClient) -> anyhow::Result<()> { - log::debug!("trying to claim task..."); - if let Some(task) = vicky_client - .do_request::<_, Option>( - Method::POST, - "api/v1/tasks/claim", - &serde_json::json!({ "features": cfg.features }), - ) - .await? - { - log::info!("task claimed: {} {} 🎉", task.id, task.display_name); - log::debug!("{:#?}", task); - - tokio::task::spawn(run_task(cfg.clone(), task)); - } else { - log::debug!("no work available..."); - } - - Ok(()) -} - -#[tokio::main(flavor = "current_thread")] -async fn run(cfg: AppConfig) -> anyhow::Result<()> { - let cfg = Arc::new(cfg); - let mut vicky_client_mgmt = HttpClient::new(cfg.clone()); - vicky_client_mgmt.renew_access_token().await?; - - log::info!("config valid, starting communication with vicky"); - log::info!("waiting for tasks..."); - - loop { - if let Err(e) = try_claim(cfg.clone(), &mut vicky_client_mgmt).await { - log::error!("{}", e); - tokio::time::sleep(std::time::Duration::from_secs(5)).await; - } - tokio::time::sleep(std::time::Duration::from_secs(1)).await; - } + tasks::runner::run(app_config) } diff --git a/fairy/src/tasks/log_sink.rs b/fairy/src/tasks/log_sink.rs new file mode 100644 index 0000000..b20bf8b --- /dev/null +++ b/fairy/src/tasks/log_sink.rs @@ -0,0 +1,42 @@ +use std::sync::Arc; + +use futures_util::Sink; +use reqwest::Method; +use uuid::Uuid; + +use crate::api::HttpClient; +use crate::config::AppConfig; + +pub fn log_sink( + cfg: Arc, + task_id: Uuid, +) -> impl Sink, Error = anyhow::Error> + Send { + let vicky_client_task = HttpClient::new(cfg.clone()); + + futures_util::sink::unfold( + vicky_client_task, + move |mut http_client, lines: Vec| async move { + let response = http_client + .do_request::<_, ()>( + Method::POST, + &format!("api/v1/tasks/{}/logs", task_id), + &serde_json::json!({ "lines": lines }), + ) + .await; + + match response { + Ok(_) => { + log::info!("logged {} line(s) from task", lines.len()); + Ok(http_client) + } + Err(e) => { + log::error!( + "could not log from task. {} lines were dropped", + lines.len() + ); + Err(e) + } + } + }, + ) +} diff --git a/fairy/src/tasks/mod.rs b/fairy/src/tasks/mod.rs new file mode 100644 index 0000000..c9e2e4b --- /dev/null +++ b/fairy/src/tasks/mod.rs @@ -0,0 +1,3 @@ +mod log_sink; +pub mod runner; +mod types; diff --git a/fairy/src/tasks/runner.rs b/fairy/src/tasks/runner.rs new file mode 100644 index 0000000..44d3b52 --- /dev/null +++ b/fairy/src/tasks/runner.rs @@ -0,0 +1,112 @@ +use std::process::Stdio; +use std::sync::Arc; + +use anyhow::anyhow; +use futures_util::{StreamExt, TryStreamExt}; +use reqwest::Method; +use tokio::process::Command; +use tokio_util::codec::{FramedRead, LinesCodec}; + +use crate::api::HttpClient; +use crate::config::AppConfig; +use crate::tasks::types::{Task, TaskResult}; + +#[tokio::main(flavor = "current_thread")] +pub async fn run(cfg: AppConfig) -> anyhow::Result<()> { + let cfg = Arc::new(cfg); + let mut vicky_client_mgmt = HttpClient::new(cfg.clone()); + vicky_client_mgmt.renew_access_token().await?; + + log::info!("config valid, starting communication with vicky"); + log::info!("waiting for tasks..."); + + loop { + if let Err(e) = try_claim(cfg.clone(), &mut vicky_client_mgmt).await { + log::error!("{}", e); + tokio::time::sleep(std::time::Duration::from_secs(5)).await; + } + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + } +} + +async fn try_run_task(cfg: Arc, task: &Task) -> anyhow::Result<()> { + let mut args = vec!["run".into(), "-L".into(), task.flake_ref.flake.clone()]; + args.extend(task.flake_ref.args.clone()); + + let mut child = Command::new("nix") + .args(args) + .env("VICKY_API_URL", &cfg.vicky_external_url) + .kill_on_drop(true) + .stdin(Stdio::null()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn()?; + + let logger = log_sink(cfg.clone(), task.id); + + let lines = futures_util::stream::select( + FramedRead::new(child.stdout.take().unwrap(), LinesCodec::new()), + FramedRead::new(child.stderr.take().unwrap(), LinesCodec::new()), + ); + + lines + .ready_chunks(1024) // TODO switch to try_ready_chunks + .map(|v| v.into_iter().collect::, _>>()) + .map_err(anyhow::Error::from) + .forward(logger) + .await?; + let exit_status = child.wait().await?; + + if exit_status.success() { + log::info!("task finished: {} {} 🎉", task.id, task.display_name); + Ok(()) + } else { + Err(anyhow!("exit code {:?}", exit_status.code())) + } +} + +async fn run_task(cfg: Arc, task: Task) { + let mut vicky_client_task = HttpClient::new(cfg.clone()); + + #[cfg(not(feature = "nixless-test-mode"))] + let result = match try_run_task(cfg.clone(), &task).await { + Err(e) => { + log::info!("task failed: {} {} {:?}", task.id, task.display_name, e); + TaskResult::Error + } + Ok(_) => TaskResult::Success, + }; + + #[cfg(feature = "nixless-test-mode")] + let result = TaskResult::Success; + + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + let _ = vicky_client_task + .do_request::<_, ()>( + Method::POST, + &format!("api/v1/tasks/{}/finish", task.id), + &serde_json::json!({ "result": result }), + ) + .await; +} + +async fn try_claim(cfg: Arc, vicky_client: &mut HttpClient) -> anyhow::Result<()> { + log::debug!("trying to claim task..."); + if let Some(task) = vicky_client + .do_request::<_, Option>( + Method::POST, + "api/v1/tasks/claim", + &serde_json::json!({ "features": cfg.features }), + ) + .await? + { + log::info!("task claimed: {} {} 🎉", task.id, task.display_name); + log::debug!("{:#?}", task); + + tokio::task::spawn(run_task(cfg.clone(), task)); + } else { + log::debug!("no work available..."); + } + + Ok(()) +} diff --git a/fairy/src/tasks/types.rs b/fairy/src/tasks/types.rs new file mode 100644 index 0000000..adf352c --- /dev/null +++ b/fairy/src/tasks/types.rs @@ -0,0 +1,33 @@ +use rocket::serde::{Deserialize, Serialize}; +use uuid::Uuid; + +// TODO: TEMPORARY!! you know what has to be done... stop wasting time and do it + +#[derive(Debug, Deserialize)] +pub struct FlakeRef { + pub flake: String, + pub args: Vec, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(tag = "result")] +pub enum TaskResult { + Success, + Error, +} + +#[derive(Debug, Deserialize)] +#[serde(tag = "state")] +pub enum TaskStatus { + New, + Running, + Finished(TaskResult), +} + +#[derive(Debug, Deserialize)] +pub struct Task { + pub id: Uuid, + pub display_name: String, + pub status: TaskStatus, + pub flake_ref: FlakeRef, +} diff --git a/fairy/src/utils.rs b/fairy/src/utils.rs new file mode 100644 index 0000000..cb4a251 --- /dev/null +++ b/fairy/src/utils.rs @@ -0,0 +1,12 @@ +use std::process::exit; + +use which::which; + +const CODE_NIX_NOT_INSTALLED: i32 = 1; + +fn ensure_nix() { + if which("nix").is_err() { + log::error!("\"nix\" binary not found. Please install nix or run on a nix-os host."); + exit(CODE_NIX_NOT_INSTALLED); + } +} From 4a54a4fb1420c324666f3b3d8d8433bf7729bb1f Mon Sep 17 00:00:00 2001 From: Kek5chen Date: Thu, 20 Jun 2024 11:29:58 +0200 Subject: [PATCH 08/21] fix: forgot these imports --- fairy/src/config.rs | 6 +++--- fairy/src/tasks/runner.rs | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/fairy/src/config.rs b/fairy/src/config.rs index bc57d40..a26ee4c 100644 --- a/fairy/src/config.rs +++ b/fairy/src/config.rs @@ -2,9 +2,9 @@ use rocket::serde::Deserialize; #[derive(Deserialize, Debug)] pub struct OIDCConfig { - issuer_url: String, - client_id: String, - client_secret: String, + pub(crate) issuer_url: String, + pub(crate) client_id: String, + pub(crate) client_secret: String, } #[derive(Deserialize, Debug)] diff --git a/fairy/src/tasks/runner.rs b/fairy/src/tasks/runner.rs index 44d3b52..c2ad5be 100644 --- a/fairy/src/tasks/runner.rs +++ b/fairy/src/tasks/runner.rs @@ -9,6 +9,7 @@ use tokio_util::codec::{FramedRead, LinesCodec}; use crate::api::HttpClient; use crate::config::AppConfig; +use crate::tasks::log_sink::log_sink; use crate::tasks::types::{Task, TaskResult}; #[tokio::main(flavor = "current_thread")] From 0f55fba729c5bffc1202266379cca6a6bc3439f2 Mon Sep 17 00:00:00 2001 From: Kek5chen Date: Thu, 20 Jun 2024 14:50:15 +0200 Subject: [PATCH 09/21] refactor: spelling --- fairy/src/api.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fairy/src/api.rs b/fairy/src/api.rs index 65aec78..bf7df63 100644 --- a/fairy/src/api.rs +++ b/fairy/src/api.rs @@ -63,7 +63,7 @@ impl HttpClient { let access_token = ccres.access_token().secret(); let expires_at = Utc::now() + ccres.expires_in().unwrap() - Duration::seconds(5); - info!("Accquired access token, expiring at {:?} ..", expires_at); + info!("Acquired access token, expiring at {:?} ..", expires_at); self.client_state = HttpClientState::Authenticated { access_token: access_token.clone(), From 8504648a13281acc5085001ae94a5f08d7823245 Mon Sep 17 00:00:00 2001 From: Kek5chen Date: Thu, 20 Jun 2024 14:51:02 +0200 Subject: [PATCH 10/21] fix: give back more complete error --- fairy/src/api.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/fairy/src/api.rs b/fairy/src/api.rs index bf7df63..5d7fa43 100644 --- a/fairy/src/api.rs +++ b/fairy/src/api.rs @@ -57,8 +57,7 @@ impl HttpClient { let ccres = ccreq .request_async(async_http_client) - .await - .map_err(|_| FairyError::Unauthorized)?; + .await?; let access_token = ccres.access_token().secret(); let expires_at = Utc::now() + ccres.expires_in().unwrap() - Duration::seconds(5); From e66d9f72f29887b2e36ee86652fcd86b93ed2a17 Mon Sep 17 00:00:00 2001 From: Kek5chen Date: Thu, 20 Jun 2024 14:51:41 +0200 Subject: [PATCH 11/21] fix: task runner error output --- fairy/src/main.rs | 16 ++++++++++++++-- fairy/src/tasks/runner.rs | 1 - 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/fairy/src/main.rs b/fairy/src/main.rs index 194e9ee..8e59723 100644 --- a/fairy/src/main.rs +++ b/fairy/src/main.rs @@ -1,5 +1,6 @@ use rocket::figment::{Figment, Profile}; use rocket::figment::providers::{Env, Format, Toml}; +use tokio::task::spawn_blocking; use crate::config::AppConfig; @@ -9,7 +10,8 @@ mod error; mod tasks; mod utils; -fn main() -> anyhow::Result<()> { +#[tokio::main] +async fn main() -> anyhow::Result<()> { env_logger::builder() .filter_level(log::LevelFilter::Debug) .init(); @@ -34,5 +36,15 @@ fn main() -> anyhow::Result<()> { )); let app_config = rocket_config_figment.extract::()?; - tasks::runner::run(app_config) + match spawn_blocking(|| tasks::runner::run(app_config)).await?.await { + Ok(_) => log::info!("Runner exited successfully."), + Err(e) => { + for l in e.chain() { // chain here for full debug info + log::error!("Runner encountered a fatal error: {:?}", l); + } + return Err(e); + } + } + + Ok(()) } diff --git a/fairy/src/tasks/runner.rs b/fairy/src/tasks/runner.rs index c2ad5be..773730b 100644 --- a/fairy/src/tasks/runner.rs +++ b/fairy/src/tasks/runner.rs @@ -12,7 +12,6 @@ use crate::config::AppConfig; use crate::tasks::log_sink::log_sink; use crate::tasks::types::{Task, TaskResult}; -#[tokio::main(flavor = "current_thread")] pub async fn run(cfg: AppConfig) -> anyhow::Result<()> { let cfg = Arc::new(cfg); let mut vicky_client_mgmt = HttpClient::new(cfg.clone()); From f746925a773fe258d06075a6a3976c374880ffb0 Mon Sep 17 00:00:00 2001 From: Kek5chen Date: Thu, 4 Jul 2024 19:11:31 +0200 Subject: [PATCH 12/21] refactor: naming --- fairy/src/api.rs | 5 ++--- fairy/src/error.rs | 2 +- vicky/src/bin/vicky/auth.rs | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/fairy/src/api.rs b/fairy/src/api.rs index 5d7fa43..084d05e 100644 --- a/fairy/src/api.rs +++ b/fairy/src/api.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use chrono::{DateTime, Duration, Utc}; -use log::info; +use log::{debug, info}; use openidconnect::{ClientId, ClientSecret, IssuerUrl, OAuth2TokenResponse, Scope}; use openidconnect::core::{CoreClient, CoreProviderMetadata}; use openidconnect::reqwest::async_http_client; @@ -10,7 +10,6 @@ use serde::de::DeserializeOwned; use serde::Serialize; use crate::AppConfig; -use crate::error::FairyError; #[derive(Debug)] pub enum HttpClientState { @@ -78,7 +77,7 @@ impl HttpClient { ) -> anyhow::Result { let now = Utc::now(); - info!("client_state: {:?}", self.client_state); + debug!("client_state: {:?}", self.client_state); let access_token_to_use = match &self.client_state { HttpClientState::Authenticated { diff --git a/fairy/src/error.rs b/fairy/src/error.rs index 0b9030b..8e4109c 100644 --- a/fairy/src/error.rs +++ b/fairy/src/error.rs @@ -3,5 +3,5 @@ use thiserror::Error; #[derive(Error, Debug)] pub enum FairyError { #[error("Could not authenticate against OIDC provider")] - Unauthorized, + OpenId, } diff --git a/vicky/src/bin/vicky/auth.rs b/vicky/src/bin/vicky/auth.rs index 4240dea..ab92038 100644 --- a/vicky/src/bin/vicky/auth.rs +++ b/vicky/src/bin/vicky/auth.rs @@ -61,7 +61,7 @@ async fn extract_user_from_token(jwks_verifier: &State, db: .send() .await?; - let user_info = x.json::().await?; + let user_info = x.json::().await?; log::info!("userinfo={:?}", user_info); From 00515d33e2243a45abe5e0ba9b46472c3c56ca52 Mon Sep 17 00:00:00 2001 From: Kek5chen Date: Thu, 4 Jul 2024 19:12:17 +0200 Subject: [PATCH 13/21] fix: check role with ends_with due to prefix --- vicky/src/bin/vicky/auth.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vicky/src/bin/vicky/auth.rs b/vicky/src/bin/vicky/auth.rs index ab92038..1a634f8 100644 --- a/vicky/src/bin/vicky/auth.rs +++ b/vicky/src/bin/vicky/auth.rs @@ -70,7 +70,7 @@ async fn extract_user_from_token(jwks_verifier: &State, db: let account_user: DbUser; match vicky_role { - Some(vicky_role) if vicky_role == "vicky:machine" => { + Some(vicky_role) if vicky_role.ends_with("machine") => { let preferred_username = match user_info.get("preferred_username").and_then(|x| x.as_str()) { Some(preferred_username) => Some(preferred_username), None => return Err(AppError::JWTFormatError("user_info must contain preferred_username".to_string())) @@ -82,7 +82,7 @@ async fn extract_user_from_token(jwks_verifier: &State, db: role: vicky_role.to_string(), }; } - Some(vicky_role) if vicky_role == "vicky:admin" => { + Some(vicky_role) if vicky_role.ends_with("admin") => { let name = match user_info.get("name").and_then(|x| x.as_str()) { Some(name) => Some(name), None => return Err(AppError::JWTFormatError("user_info must contain name".to_string())) From c8cca1f4d00c7594d952b0961446815a4a4e1070 Mon Sep 17 00:00:00 2001 From: Kek5chen Date: Thu, 4 Jul 2024 19:12:29 +0200 Subject: [PATCH 14/21] fix: should always have a space --- vicky/src/bin/vicky/auth.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vicky/src/bin/vicky/auth.rs b/vicky/src/bin/vicky/auth.rs index 1a634f8..97dd1e9 100644 --- a/vicky/src/bin/vicky/auth.rs +++ b/vicky/src/bin/vicky/auth.rs @@ -128,7 +128,7 @@ impl<'r> request::FromRequest<'r> for User { .expect("request OIDCConfigResolved"); if let Some(auth_header) = request.headers().get_one("Authorization") { - if !auth_header.starts_with("Bearer") { + if !auth_header.starts_with("Bearer ") { return request::Outcome::Forward(Status::Forbidden); } From 14477f6ab70def3ad114ceccc7b034da09ff2c34 Mon Sep 17 00:00:00 2001 From: Kek5chen Date: Fri, 5 Jul 2024 14:54:28 +0200 Subject: [PATCH 15/21] refactor: use bearer_auth --- fairy/src/api.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fairy/src/api.rs b/fairy/src/api.rs index 084d05e..d18e14a 100644 --- a/fairy/src/api.rs +++ b/fairy/src/api.rs @@ -96,7 +96,7 @@ impl HttpClient { Ok(self .http_client .request(method, url) - .header("Authorization", format!("Bearer {}", access_token_to_use))) + .bearer_auth(access_token_to_use)) } pub async fn do_request( From b35d4265ad4d8631e6241821e1baa5743b76a012 Mon Sep 17 00:00:00 2001 From: Kek5chen Date: Fri, 5 Jul 2024 14:55:12 +0200 Subject: [PATCH 16/21] refactor: content type is set through json(_) --- fairy/src/api.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/fairy/src/api.rs b/fairy/src/api.rs index d18e14a..d836aa4 100644 --- a/fairy/src/api.rs +++ b/fairy/src/api.rs @@ -111,7 +111,6 @@ impl HttpClient { format!("{}/{}", self.app_config.vicky_url, endpoint), ) .await? - .header("content-type", "application/json") .json(q) .send() .await?; From 8a6b8196d8bb26362424e0607906497dab601c98 Mon Sep 17 00:00:00 2001 From: Kek5chen Date: Fri, 5 Jul 2024 16:53:42 +0200 Subject: [PATCH 17/21] refactor: use auth constant from rocket --- vicky/src/bin/vicky/auth.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vicky/src/bin/vicky/auth.rs b/vicky/src/bin/vicky/auth.rs index 97dd1e9..97a2401 100644 --- a/vicky/src/bin/vicky/auth.rs +++ b/vicky/src/bin/vicky/auth.rs @@ -4,6 +4,7 @@ use jwtk::jwk::RemoteJwksVerifier; use log::{warn}; use rocket::http::Status; use rocket::{request, State}; +use rocket::http::hyper::header::AUTHORIZATION; use serde::Deserialize; use serde_json::{Map, Value}; use uuid::Uuid; @@ -127,7 +128,7 @@ impl<'r> request::FromRequest<'r> for User { .await .expect("request OIDCConfigResolved"); - if let Some(auth_header) = request.headers().get_one("Authorization") { + if let Some(auth_header) = request.headers().get_one(AUTHORIZATION.as_str()) { if !auth_header.starts_with("Bearer ") { return request::Outcome::Forward(Status::Forbidden); } From 774b41eb4488a0164aad0ed6663d0d65c54a6c11 Mon Sep 17 00:00:00 2001 From: Kek5chen Date: Fri, 5 Jul 2024 16:59:05 +0200 Subject: [PATCH 18/21] refactor: use ends_with in role parser --- vicky/src/bin/vicky/auth.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/vicky/src/bin/vicky/auth.rs b/vicky/src/bin/vicky/auth.rs index 97a2401..44bc2df 100644 --- a/vicky/src/bin/vicky/auth.rs +++ b/vicky/src/bin/vicky/auth.rs @@ -26,10 +26,12 @@ impl FromStr for Role { type Err = (); fn from_str(s: &str) -> Result { - match s { - "vicky:admin" => Ok(Self::Admin), - "vicky:machine" => Ok(Self::Machine), - _ => Err(()), + if s.ends_with("admin") { + Ok(Self::Admin) + } else if s.ends_with("machine") { + Ok(Self::Machine) + } else { + Err(()) } } } From 9301e65fbea6c99667d96b1a59d94680c29e376e Mon Sep 17 00:00:00 2001 From: Kek5chen Date: Fri, 5 Jul 2024 17:22:45 +0200 Subject: [PATCH 19/21] fix: handle opt safer --- vicky/src/bin/vicky/main.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/vicky/src/bin/vicky/main.rs b/vicky/src/bin/vicky/main.rs index b84afc9..9959d06 100644 --- a/vicky/src/bin/vicky/main.rs +++ b/vicky/src/bin/vicky/main.rs @@ -4,10 +4,10 @@ use aws_sdk_s3::config::{BehaviorVersion, Credentials, Region}; use aws_sdk_s3::error::SdkError; use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness}; use jwtk::jwk::RemoteJwksVerifier; +use rocket::{Build, Rocket, routes}; use rocket::fairing::AdHoc; -use rocket::figment::providers::{Env, Format, Toml}; use rocket::figment::{Figment, Profile}; -use rocket::{routes, Build, Rocket}; +use rocket::figment::providers::{Env, Format, Toml}; use serde::{Deserialize, Serialize}; use tokio::sync::broadcast; @@ -89,7 +89,13 @@ fn run_migrations(connection: &mut impl MigrationHarness) -> Res } async fn run_rocket_migrations(rocket: Rocket) -> Result, Rocket> { - let db: Database = Database::get_one(&rocket).await.unwrap(); + let opt_db = Database::get_one(&rocket).await; + + let db = match opt_db { + Some(db) => db, + None => return Err(rocket) + }; + match db.run(run_migrations).await { Ok(_) => Ok(rocket), Err(_) => Err(rocket), From fef1092b3df04ac926111323520d40d5dec55a32 Mon Sep 17 00:00:00 2001 From: Kek5chen Date: Fri, 5 Jul 2024 17:23:24 +0200 Subject: [PATCH 20/21] fix: no need to unwrap all that --- vicky/src/bin/vicky/auth.rs | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/vicky/src/bin/vicky/auth.rs b/vicky/src/bin/vicky/auth.rs index 44bc2df..6efb712 100644 --- a/vicky/src/bin/vicky/auth.rs +++ b/vicky/src/bin/vicky/auth.rs @@ -47,11 +47,11 @@ async fn extract_user_from_token(jwks_verifier: &State, db: let jwt = jwks_verifier.verify::>(token).await?; let sub = match &jwt.claims().sub { - Some(sub) => Some(Uuid::from_str(sub)?), - None => return Err(AppError::JWTFormatError("JWT must contain sub".to_string())) + Some(sub) => Uuid::from_str(sub)?, + None => return Err(AppError::JWTFormatError("JWT must contain sub".to_string())), }; - let user = db.run(move |conn| conn.get_user(sub.unwrap())).await?; + let user = db.run(move |conn| conn.get_user(sub)).await?; match user { Some(user) => { @@ -75,30 +75,30 @@ async fn extract_user_from_token(jwks_verifier: &State, db: match vicky_role { Some(vicky_role) if vicky_role.ends_with("machine") => { let preferred_username = match user_info.get("preferred_username").and_then(|x| x.as_str()) { - Some(preferred_username) => Some(preferred_username), - None => return Err(AppError::JWTFormatError("user_info must contain preferred_username".to_string())) + Some(preferred_username) => preferred_username, + None => return Err(AppError::JWTFormatError("user_info must contain preferred_username".to_string())), }; account_user = DbUser { - sub: sub.unwrap(), - name: preferred_username.unwrap().to_string(), + sub, + name: preferred_username.to_string(), role: vicky_role.to_string(), }; } Some(vicky_role) if vicky_role.ends_with("admin") => { let name = match user_info.get("name").and_then(|x| x.as_str()) { - Some(name) => Some(name), - None => return Err(AppError::JWTFormatError("user_info must contain name".to_string())) + Some(name) => name, + None => return Err(AppError::JWTFormatError("user_info must contain name".to_string())), }; account_user = DbUser { - sub: sub.unwrap(), - name: name.unwrap().to_string(), + sub, + name: name.to_string(), role: vicky_role.to_string(), }; } _ => { - return Err(AppError::UserAccountError("vicky_roles was not filled".to_string())) + return Err(AppError::UserAccountError("vicky_roles was not filled".to_string())); } } @@ -142,6 +142,8 @@ impl<'r> request::FromRequest<'r> for User { let user = User { id: user.sub, full_name: user.name, + + // extract_user_from_token should never return an invalid .role object that doesn't end with "admin" or "machine" role: Role::from_str(&user.role).unwrap() }; From 1d679080ede1f48526d31ad97afbe2a59c1c8d54 Mon Sep 17 00:00:00 2001 From: Kek5chen Date: Fri, 5 Jul 2024 17:23:54 +0200 Subject: [PATCH 21/21] fix: don't crash if auth provider doesn't provide expires in --- fairy/src/api.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fairy/src/api.rs b/fairy/src/api.rs index d836aa4..a18f2f3 100644 --- a/fairy/src/api.rs +++ b/fairy/src/api.rs @@ -59,7 +59,7 @@ impl HttpClient { .await?; let access_token = ccres.access_token().secret(); - let expires_at = Utc::now() + ccres.expires_in().unwrap() - Duration::seconds(5); + let expires_at = Utc::now() + ccres.expires_in().unwrap_or_default() - Duration::seconds(5); info!("Acquired access token, expiring at {:?} ..", expires_at);