From 3b177b550295c44464e174e0b607fda633bb0d6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABlle=20Huisman?= Date: Sun, 15 Mar 2026 12:34:30 +0100 Subject: [PATCH] feat: add distinct global actions and method actions --- Cargo.lock | 215 +++++++++++------- Cargo.toml | 7 +- deny.toml | 1 + examples/axum/Cargo.toml | 2 +- examples/dioxus-axum/Cargo.toml | 2 +- examples/leptos-actix/Cargo.toml | 2 +- examples/leptos-axum/Cargo.toml | 2 +- examples/sea-orm/Cargo.toml | 4 +- packages/core/shield/Cargo.toml | 2 +- packages/core/shield/src/action.rs | 60 ++--- packages/core/shield/src/actions/sign_out.rs | 56 +++-- packages/core/shield/src/method.rs | 20 +- packages/core/shield/src/path.rs | 12 +- packages/core/shield/src/request.rs | 30 +++ packages/core/shield/src/session.rs | 50 ++-- packages/core/shield/src/shield.rs | 117 +++++++--- packages/core/shield/src/shield_dyn.rs | 41 ++-- .../integrations/shield-axum/src/router.rs | 24 +- .../shield-axum/src/routes/action.rs | 55 ++++- .../integrations/shield-dioxus/src/lib.rs | 2 +- .../shield-dioxus/src/routes/action.rs | 42 +++- .../shield-leptos/src/routes/action.rs | 46 +++- .../methods/shield-credentials/src/actions.rs | 2 - .../shield-credentials/src/actions/sign_in.rs | 17 +- .../src/actions/sign_out.rs | 55 ----- .../methods/shield-credentials/src/method.rs | 15 +- packages/methods/shield-dummy/src/actions.rs | 2 - .../shield-dummy/src/actions/sign_in.rs | 19 +- .../shield-dummy/src/actions/sign_out.rs | 55 ----- packages/methods/shield-dummy/src/method.rs | 14 +- packages/methods/shield-email/src/actions.rs | 2 - .../shield-email/src/actions/sign_in.rs | 14 +- .../src/actions/sign_in_callback.rs | 18 +- .../shield-email/src/actions/sign_out.rs | 55 ----- packages/methods/shield-email/src/method.rs | 7 +- packages/methods/shield-oauth/Cargo.toml | 4 +- packages/methods/shield-oauth/src/actions.rs | 2 - .../shield-oauth/src/actions/sign_in.rs | 37 +-- .../src/actions/sign_in_callback.rs | 33 +-- .../shield-oauth/src/actions/sign_out.rs | 57 ----- packages/methods/shield-oauth/src/method.rs | 9 +- packages/methods/shield-oidc/Cargo.toml | 4 +- packages/methods/shield-oidc/src/actions.rs | 1 - .../shield-oidc/src/actions/sign_in.rs | 39 ++-- .../src/actions/sign_in_callback.rs | 35 +-- .../shield-oidc/src/actions/sign_out.rs | 158 +++++-------- packages/methods/shield-oidc/src/method.rs | 7 +- packages/methods/shield-workos/src/actions.rs | 2 - .../shield-workos/src/actions/index.rs | 14 +- .../shield-workos/src/actions/sign_in.rs | 14 +- .../shield-workos/src/actions/sign_out.rs | 69 ------ .../shield-workos/src/actions/sign_up.rs | 14 +- packages/methods/shield-workos/src/method.rs | 9 +- .../styles/shield-bootstrap/src/dioxus.rs | 12 +- .../shield-bootstrap/src/dioxus/form.rs | 6 +- .../src/dioxus/method_form.rs | 78 +++++++ .../styles/shield-bootstrap/src/leptos.rs | 12 +- .../shield-bootstrap/src/leptos/form.rs | 9 +- .../src/leptos/method_form.rs | 26 +++ 59 files changed, 888 insertions(+), 830 deletions(-) delete mode 100644 packages/methods/shield-credentials/src/actions/sign_out.rs delete mode 100644 packages/methods/shield-dummy/src/actions/sign_out.rs delete mode 100644 packages/methods/shield-email/src/actions/sign_out.rs delete mode 100644 packages/methods/shield-oauth/src/actions/sign_out.rs delete mode 100644 packages/methods/shield-workos/src/actions/sign_out.rs create mode 100644 packages/styles/shield-bootstrap/src/dioxus/method_form.rs create mode 100644 packages/styles/shield-bootstrap/src/leptos/method_form.rs diff --git a/Cargo.lock b/Cargo.lock index 3910489..40c2ab4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1074,7 +1074,7 @@ dependencies = [ "pathdiff", "serde_core", "toml", - "winnow 0.7.13", + "winnow 0.7.15", ] [[package]] @@ -1099,7 +1099,17 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad7154afa56de2f290e3c82c2c6dc4f5b282b6870903f56ef3509aba95866edc" dependencies = [ - "const-serialize-macro", + "const-serialize-macro 0.7.2", +] + +[[package]] +name = "const-serialize" +version = "0.8.0-alpha.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e42cd5aabba86f128b3763da1fec1491c0f728ce99245062cd49b6f9e6d235b" +dependencies = [ + "const-serialize 0.7.2", + "const-serialize-macro 0.8.0-alpha.0", "serde", ] @@ -1114,6 +1124,17 @@ dependencies = [ "syn 2.0.108", ] +[[package]] +name = "const-serialize-macro" +version = "0.8.0-alpha.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42571ed01eb46d2e1adcf99c8ca576f081e46f2623d13500eba70d1d99a4c439" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", +] + [[package]] name = "const-str" version = "0.7.0" @@ -1514,9 +1535,9 @@ dependencies = [ [[package]] name = "dioxus" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a115f9dbe5900c6044ee6a791e1b160c29989c6a8721eec099e01a964e5dae4" +checksum = "92b583b48ac77158495e6678fe3a2b5954fc8866fc04cb9695dd146e88bc329d" dependencies = [ "dioxus-asset-resolver", "dioxus-cli-config", @@ -1547,9 +1568,9 @@ dependencies = [ [[package]] name = "dioxus-asset-resolver" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6851ae49ba3988f1b77f6ef826eb142e811602129841c24bf5a4e103708d9844" +checksum = "c0161af1d3cfc8ff31503ff1b7ee0068c97771fc38d0cc6566e23483142ddf4f" dependencies = [ "dioxus-cli-config", "http 1.4.0", @@ -1568,18 +1589,18 @@ dependencies = [ [[package]] name = "dioxus-cli-config" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59e9d9da2e7334fdae5d77e3989207aa549062f74ff1ca2171393bbdd7fda90" +checksum = "ccd67ab405e1915a47df9769cd5408545d1b559d5c01ce7a0f442caef520d1f3" dependencies = [ "wasm-bindgen", ] [[package]] name = "dioxus-config-macro" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bd56be5ea6c9f416b25e9e3adc910c02127be75b6d1ecd567661f31920b27ba" +checksum = "f040ec7c41aa5428283f56bb0670afba9631bfe3ffd885f4814807f12c8c9d91" dependencies = [ "proc-macro2", "quote", @@ -1587,15 +1608,15 @@ dependencies = [ [[package]] name = "dioxus-config-macros" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c49327465c2d434d00fb4c86bd35ae72155b479622e09352b950d9ab4807bf23" +checksum = "10c41b47b55a433b61f7c12327c85ba650572bacbcc42c342ba2e87a57975264" [[package]] name = "dioxus-core" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7400cbd21a98e585a13f8c29574da9b8afb2fd343f712618042b6c71761f0933" +checksum = "b389b0e3cc01c7da292ad9b884b088835fdd1671d45fbd2f737506152b22eef0" dependencies = [ "anyhow", "const_format", @@ -1615,9 +1636,9 @@ dependencies = [ [[package]] name = "dioxus-core-macro" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51c0eb7eb76dd5a0b9a116d94d29ca78924a1ed1fcb7ea072eda5045d3ac056" +checksum = "6a82d65f0024fc86f01911a16156d280eea583be5a82a3bed85e7e8e4194302d" dependencies = [ "convert_case 0.8.0", "dioxus-rsx", @@ -1628,15 +1649,15 @@ dependencies = [ [[package]] name = "dioxus-core-types" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0652ab5f9c2c32261d44a3155debbfd909ed03d03434d7f70f5a796bf255c519" +checksum = "bfc4b8cdc440a55c17355542fc2089d97949bba674255d84cac77805e1db8c9f" [[package]] name = "dioxus-devtools" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9748128bcd102b10e58c765939807053ccab542206a939b8bab228077455c259" +checksum = "dcf89488bad8fb0f18b9086ee2db01f95f709801c10c68be42691a36378a0f2d" dependencies = [ "dioxus-cli-config", "dioxus-core", @@ -1654,9 +1675,9 @@ dependencies = [ [[package]] name = "dioxus-devtools-types" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48540ca8a0ab1ec81cd4db35f0c9713d43b158647fc1dcb0d79965fc3b41d96c" +checksum = "6e7381d9d7d0a0f66b9d5082d584853c3d53be21d34007073daca98ddf26fc4d" dependencies = [ "dioxus-core", "serde", @@ -1665,9 +1686,9 @@ dependencies = [ [[package]] name = "dioxus-document" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501a189b391d091c9aa02c05f5b25f5d0d17fa0e1016e000b0fdbb073d77cd6a" +checksum = "6ba0aeeff26d9d06441f59fd8d7f4f76098ba30ca9728e047c94486161185ceb" dependencies = [ "dioxus-core", "dioxus-core-macro", @@ -1684,9 +1705,9 @@ dependencies = [ [[package]] name = "dioxus-fullstack" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54150804265defdb21a6f2d8914a45316a1e7fb70ab22c30cf836e8fe2f8081b" +checksum = "7db1f8b70338072ec408b48d09c96559cf071f87847465d8161294197504c498" dependencies = [ "anyhow", "async-stream", @@ -1749,9 +1770,9 @@ dependencies = [ [[package]] name = "dioxus-fullstack-core" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a9be2ef4d701520eefef284d218fb35b159dccd6bccc02b5bad42945e2599d" +checksum = "cda8b152e85121243741b9d5f2a3d8cb3c47a7b2299e902f98b6a7719915b0a2" dependencies = [ "anyhow", "axum-core", @@ -1777,9 +1798,9 @@ dependencies = [ [[package]] name = "dioxus-fullstack-macro" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31ea4451fe8c9d2af24fb718a94966d5fd7e11325777e5b5a59085c5c85e5fb" +checksum = "255104d4a4f278f1a8482fa30536c91d22260c561c954b753e72987df8d65b2e" dependencies = [ "const_format", "convert_case 0.8.0", @@ -1791,9 +1812,9 @@ dependencies = [ [[package]] name = "dioxus-history" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55d704b3ba9504cb3c9cde49499b75546d1faaff2736f4c368aca6c061c48ac3" +checksum = "8d00ba43bfe6e5ca226fef6128f240ca970bea73cac0462416188026360ccdcf" dependencies = [ "dioxus-core", "tracing", @@ -1801,9 +1822,9 @@ dependencies = [ [[package]] name = "dioxus-hooks" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79c6d68be372eca8186a1c57ec49be67a6ea46022150b5e85ab6a6acde52d272" +checksum = "dab2da4f038c33cb38caa37ffc3f5d6dfbc018f05da35b238210a533bb075823" dependencies = [ "dioxus-core", "dioxus-signals", @@ -1817,9 +1838,9 @@ dependencies = [ [[package]] name = "dioxus-html" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3aa87ecfa0f38ec286be25789a7f2d6c30778111f1fbff563da4bae41d171496" +checksum = "eded5fa6d2e677b7442a93f4228bf3c0ad2597a8bd3292cae50c869d015f3a99" dependencies = [ "async-trait", "bytes", @@ -1844,9 +1865,9 @@ dependencies = [ [[package]] name = "dioxus-html-internal-macro" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49301d0e389378e8070b8b704110339a0d3358efad9f5ad483ffab3a8d406dae" +checksum = "45462ab85fe059a36841508d40545109fd0e25855012d22583a61908eb5cd02a" dependencies = [ "convert_case 0.8.0", "proc-macro2", @@ -1856,9 +1877,9 @@ dependencies = [ [[package]] name = "dioxus-interpreter-js" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5437a89d3ef7edfebc0f10acb065f1709cb7ffb678e3a4bb1416706d71f7c67" +checksum = "a42a7f73ad32a5054bd8c1014f4ac78cca3b7f6889210ee2b57ea31b33b6d32f" dependencies = [ "dioxus-core", "dioxus-core-types", @@ -1875,9 +1896,9 @@ dependencies = [ [[package]] name = "dioxus-liveview" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f690466a88cc93d7f87e1735aab9cb4a83c70f452ed344a32559577e80505da4" +checksum = "a3f7a1cfe6f8e9f2e303607c8ae564d11932fd80714c8a8c97e3860d55538997" dependencies = [ "axum", "dioxus-cli-config", @@ -1903,9 +1924,9 @@ dependencies = [ [[package]] name = "dioxus-logger" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b25ebfbc193cebcf5af5e19b8ee7c6adee486fbd1c12f11aea058b464da16f9" +checksum = "f1eeab114cb009d9e6b85ea10639a18cfc54bb342f3b837770b004c4daeb89c2" dependencies = [ "dioxus-cli-config", "tracing", @@ -1915,9 +1936,9 @@ dependencies = [ [[package]] name = "dioxus-router" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18282604175f38d8c9291946ad6b34899657e47aef994fbbe6defb501a000f33" +checksum = "1d5b31f9e27231389bf5a117b7074d22d8c58358b484a2558e56fbab20e64ca4" dependencies = [ "dioxus-cli-config", "dioxus-core", @@ -1936,9 +1957,9 @@ dependencies = [ [[package]] name = "dioxus-router-macro" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47340b339c2c3f042b190f541b7241e2547b2e703f813d34ea24b963330c6757" +checksum = "838b9b441a95da62b39cae4defd240b5ebb0ec9f2daea1126099e00a838dc86f" dependencies = [ "base16", "digest", @@ -1951,9 +1972,9 @@ dependencies = [ [[package]] name = "dioxus-rsx" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19d97c02689beff55767ba5f6e185ffd204c6a193e372f0fead8a3722c6f7eea" +checksum = "53128858f0ccca9de54292a4d48409fda1df75fd5012c6243f664042f0225d68" dependencies = [ "proc-macro2", "proc-macro2-diagnostics", @@ -1964,9 +1985,9 @@ dependencies = [ [[package]] name = "dioxus-server" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d608c33c39f032469c6eb59f361dc2724799724d8b3e15c824d1047e664c087" +checksum = "d8adb2d4e0f0f3a157bda6af2d90f22bac40070e509a66e3ea58abf3b35f904c" dependencies = [ "anyhow", "async-trait", @@ -2022,9 +2043,9 @@ dependencies = [ [[package]] name = "dioxus-signals" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27fc4df7a31a7f02e5a0b40884bb66ee165226a05d75fce03baa44029e438762" +checksum = "2f48020bc23bc9766e7cce986c0fd6de9af0b8cbfd432652ec6b1094439c1ec6" dependencies = [ "dioxus-core", "futures-channel", @@ -2038,9 +2059,9 @@ dependencies = [ [[package]] name = "dioxus-ssr" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "088efddedd39fc29d007bc91c8a61b25130355149ea5313469f96fb695c5e3ab" +checksum = "44cf9294a21fcd1098e02ad7a3ba61b99be8310ad3395fecf8210387c83f26b9" dependencies = [ "askama_escape", "dioxus-core", @@ -2050,9 +2071,9 @@ dependencies = [ [[package]] name = "dioxus-stores" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2dec3cd677078824a733de25ddbe8e987cfc8d98aec29b7d199e1fdb8452b96" +checksum = "77aaa9ac56d781bb506cf3c0d23bea96b768064b89fe50d3b4d4659cc6bd8058" dependencies = [ "dioxus-core", "dioxus-signals", @@ -2062,9 +2083,9 @@ dependencies = [ [[package]] name = "dioxus-stores-macro" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9b7f085e374aaaa78403227b9bd83675c4078388d41a41b67dfbe4aa0bb64d5" +checksum = "5b1a728622e7b63db45774f75e71504335dd4e6115b235bbcff272980499493a" dependencies = [ "convert_case 0.8.0", "proc-macro2", @@ -2074,9 +2095,9 @@ dependencies = [ [[package]] name = "dioxus-web" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "315009f3a77c3c813415b3b8a8ea62a4d7a32dde9a666664b30862d4386e8456" +checksum = "3b33fe739fed4e8143dac222a9153593f8e2451662ce8fc4c9d167a9d6ec0923" dependencies = [ "dioxus-cli-config", "dioxus-core", @@ -2526,9 +2547,9 @@ dependencies = [ [[package]] name = "generational-box" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e658d10252a15200ca4a1c67c7180fc0baffa3f92869bbd903025daf6f70fd65" +checksum = "cc4ed190b9de8e734d47a70be59b1e7588b9e8e0d0036e332f4c014e8aed1bc5" dependencies = [ "parking_lot", "tracing", @@ -2960,7 +2981,7 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", - "webpki-roots", + "webpki-roots 1.0.1", ] [[package]] @@ -3359,9 +3380,9 @@ checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" [[package]] name = "lazy-js-bundle" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21972afec4627b7ba0de60b5269585b5ac2f56d559b0696f57eee6daf8a51b68" +checksum = "c7b88b715ab1496c6e6b8f5e927be961c4235196121b6ae59bcb51077a21dd36" [[package]] name = "lazy_static" @@ -3748,32 +3769,35 @@ dependencies = [ [[package]] name = "manganis" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97c63ae68d25457a579b7714806088c5cb44c536cf624a53a17184878f9f0bcd" +checksum = "6cce7d688848bf9d034168513b9a2ffbfe5f61df2ff14ae15e6cfc866efdd344" dependencies = [ - "const-serialize", + "const-serialize 0.7.2", + "const-serialize 0.8.0-alpha.0", "manganis-core", "manganis-macro", ] [[package]] name = "manganis-core" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d071660b149f985cbab8b23f2004ea6dd5cf947b63a0843f0e2f46e6af7229" +checksum = "84ce917b978268fe8a7db49e216343ec7c8f471f7e686feb70940d67293f19d4" dependencies = [ - "const-serialize", + "const-serialize 0.7.2", + "const-serialize 0.8.0-alpha.0", "dioxus-cli-config", "dioxus-core-types", "serde", + "winnow 0.7.15", ] [[package]] name = "manganis-macro" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9793d1d33778245b4240c330a8f575d208ce077c7e7bab1c79064252ddd4a162" +checksum = "ad513e990f7c0bca86aa68659a7a3dc4c705572ed4c22fd6af32ccf261334cc2" dependencies = [ "dunce", "macro-string", @@ -4208,6 +4232,15 @@ dependencies = [ "num-traits", ] +[[package]] +name = "ordered_hash_map" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6c699f8a30f345785be969deed7eee4c73a5de58c7faf61d6a3251ef798ff61" +dependencies = [ + "hashbrown 0.15.4", +] + [[package]] name = "ouroboros" version = "0.18.5" @@ -4927,7 +4960,7 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams 0.4.2", "web-sys", - "webpki-roots", + "webpki-roots 1.0.1", ] [[package]] @@ -5775,7 +5808,7 @@ dependencies = [ "bon", "chrono", "convert_case 0.11.0", - "futures", + "ordered_hash_map", "serde", "serde_json", "thiserror 2.0.18", @@ -6306,10 +6339,10 @@ dependencies = [ "indexmap 2.13.0", "log", "memchr", - "native-tls", "once_cell", "percent-encoding", "rust_decimal", + "rustls", "serde", "serde_json", "sha2", @@ -6321,6 +6354,7 @@ dependencies = [ "tracing", "url", "uuid", + "webpki-roots 0.26.11", ] [[package]] @@ -6515,9 +6549,9 @@ checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" [[package]] name = "subsecond" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c09bc2c9ef0381b403ab8b58122961cb83266d16b1f55f9486d5857ba4a9ae26" +checksum = "8438668e545834d795d04c4335aafc332ce046106521a29f0a5c6501de34187c" dependencies = [ "js-sys", "libc", @@ -6534,9 +6568,9 @@ dependencies = [ [[package]] name = "subsecond-types" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d07aa455c66ddfdbb51507537402b961e027846468954ef8d974bce65dff9eb0" +checksum = "1e72f747606fc19fe81d6c59e491af93ed7dcbcb6aad9d1d18b05129914ec298" dependencies = [ "serde", ] @@ -6909,7 +6943,7 @@ dependencies = [ "serde_spanned", "toml_datetime 0.7.3", "toml_parser", - "winnow 0.7.13", + "winnow 0.7.15", ] [[package]] @@ -6944,7 +6978,7 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" dependencies = [ - "winnow 0.7.13", + "winnow 0.7.15", ] [[package]] @@ -7670,6 +7704,15 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "webpki-roots" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.1", +] + [[package]] name = "webpki-roots" version = "1.0.1" @@ -8008,9 +8051,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.7.13" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" dependencies = [ "memchr", ] diff --git a/Cargo.toml b/Cargo.toml index fa8f96b..c8019b1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,10 +40,9 @@ axum = "0.8.1" bon = "3.3.2" chrono = "0.4.39" console_error_panic_hook = "0.1.2" -dioxus = "0.7.0-rc.3" -dioxus-html = "0.7.0-rc.3" -dioxus-server = "0.7.0-rc.3" -futures = "0.3.31" +dioxus = "0.7.3" +dioxus-html = "0.7.3" +dioxus-server = "0.7.3" http = "1.2.0" jsonwebtoken = "10.3.0" leptos = "0.8.3" diff --git a/deny.toml b/deny.toml index 8e9bda3..d6f1925 100644 --- a/deny.toml +++ b/deny.toml @@ -16,6 +16,7 @@ wildcards = "deny" [licenses] allow = [ "Apache-2.0", + "BSD-2-Clause", "BSD-3-Clause", "BSL-1.0", "CC0-1.0", diff --git a/examples/axum/Cargo.toml b/examples/axum/Cargo.toml index 381a85e..b6b8010 100644 --- a/examples/axum/Cargo.toml +++ b/examples/axum/Cargo.toml @@ -15,7 +15,7 @@ shield.workspace = true shield-axum = { workspace = true, features = ["utoipa"] } shield-email = { workspace = true, features = ["sender-tracing"] } shield-memory = { workspace = true, features = ["method-email", "method-oidc"] } -shield-oidc = { workspace = true, features = ["native-tls"] } +shield-oidc.workspace = true time = "0.3.47" tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } tower-sessions.workspace = true diff --git a/examples/dioxus-axum/Cargo.toml b/examples/dioxus-axum/Cargo.toml index ab76ee9..988a3e6 100644 --- a/examples/dioxus-axum/Cargo.toml +++ b/examples/dioxus-axum/Cargo.toml @@ -39,7 +39,7 @@ shield-email = { workspace = true, features = [ "sender-tracing", ], optional = true } shield-memory = { workspace = true, optional = true } -shield-oidc = { workspace = true, features = ["native-tls"], optional = true } +shield-oidc = { workspace = true, optional = true } tokio = { workspace = true, features = [ "macros", "rt-multi-thread", diff --git a/examples/leptos-actix/Cargo.toml b/examples/leptos-actix/Cargo.toml index 4c2acd1..08e7635 100644 --- a/examples/leptos-actix/Cargo.toml +++ b/examples/leptos-actix/Cargo.toml @@ -59,7 +59,7 @@ shield-email = { workspace = true, features = [ shield-leptos.workspace = true shield-leptos-actix = { workspace = true, optional = true } shield-memory = { workspace = true, optional = true } -shield-oidc = { workspace = true, features = ["native-tls"], optional = true } +shield-oidc = { workspace = true, optional = true } tracing.workspace = true tracing-subscriber.workspace = true wasm-bindgen.workspace = true diff --git a/examples/leptos-axum/Cargo.toml b/examples/leptos-axum/Cargo.toml index fe69b3a..7be4f12 100644 --- a/examples/leptos-axum/Cargo.toml +++ b/examples/leptos-axum/Cargo.toml @@ -55,7 +55,7 @@ shield-email = { workspace = true, features = [ shield-leptos.workspace = true shield-leptos-axum = { workspace = true, optional = true } shield-memory = { workspace = true, optional = true } -shield-oidc = { workspace = true, features = ["native-tls"], optional = true } +shield-oidc = { workspace = true, optional = true } time = "0.3.47" tokio = { workspace = true, features = [ "macros", diff --git a/examples/sea-orm/Cargo.toml b/examples/sea-orm/Cargo.toml index b09bedb..42c58d0 100644 --- a/examples/sea-orm/Cargo.toml +++ b/examples/sea-orm/Cargo.toml @@ -12,13 +12,13 @@ version.workspace = true [dependencies] sea-orm = { workspace = true, features = [ "macros", - "runtime-tokio-native-tls", + "runtime-tokio-rustls", "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", ] } sea-orm-migration = { workspace = true, features = [ - "runtime-tokio-native-tls", + "runtime-tokio-rustls", "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", diff --git a/packages/core/shield/Cargo.toml b/packages/core/shield/Cargo.toml index 8b74e4a..36d08a4 100644 --- a/packages/core/shield/Cargo.toml +++ b/packages/core/shield/Cargo.toml @@ -17,7 +17,7 @@ async-trait.workspace = true bon.workspace = true chrono = { workspace = true, features = ["serde"] } convert_case = "0.11.0" -futures.workspace = true +ordered_hash_map = "0.5.0" serde = { workspace = true, features = ["derive"] } serde_json.workspace = true thiserror.workspace = true diff --git a/packages/core/shield/src/action.rs b/packages/core/shield/src/action.rs index 2982470..e8b67e1 100644 --- a/packages/core/shield/src/action.rs +++ b/packages/core/shield/src/action.rs @@ -4,41 +4,28 @@ use crate::{ error::ShieldError, form::Form, provider::Provider, - request::Request, + request::{Request, RequestMethod}, response::Response, session::{BaseSession, MethodSession}, }; use async_trait::async_trait; use serde::{Deserialize, Serialize}; -#[cfg(feature = "utoipa")] -use utoipa::openapi::HttpMethod; - -#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] -pub enum ActionMethod { - Get, - Post, - Put, - Delete, - Options, - Head, - Patch, - Trace, -} -#[cfg(feature = "utoipa")] -impl From for HttpMethod { - fn from(value: ActionMethod) -> Self { - match value { - ActionMethod::Get => Self::Get, - ActionMethod::Post => Self::Post, - ActionMethod::Put => Self::Put, - ActionMethod::Delete => Self::Delete, - ActionMethod::Options => Self::Options, - ActionMethod::Head => Self::Head, - ActionMethod::Patch => Self::Patch, - ActionMethod::Trace => Self::Trace, - } - } +#[async_trait] +pub trait Action: Send + Sync { + fn id(&self) -> &'static str; + + fn name(&self) -> &'static str; + + fn openapi_summary(&self) -> &'static str; + + fn openapi_description(&self) -> &'static str; + + fn method(&self) -> RequestMethod; + + async fn forms(&self) -> Result, ShieldError>; + + async fn call(&self, session: &BaseSession, request: Request) -> Result; } // TODO: Think of a better name. @@ -48,6 +35,7 @@ impl From for HttpMethod { pub struct ActionForms { pub id: String, pub name: String, + pub forms: Vec
, pub method_forms: Vec, } @@ -70,7 +58,7 @@ pub struct ActionProviderForm { } #[async_trait] -pub trait Action: ErasedAction + Send + Sync { +pub trait MethodAction: ErasedMethodAction + Send + Sync { fn id(&self) -> String; fn name(&self) -> String; @@ -79,7 +67,7 @@ pub trait Action: ErasedAction + Send + Sync { fn openapi_description(&self) -> &'static str; - fn method(&self) -> ActionMethod; + fn method(&self) -> RequestMethod; fn condition(&self, _provider: &P, _session: &MethodSession) -> Result { Ok(true) @@ -96,7 +84,7 @@ pub trait Action: ErasedAction + Send + Sync { } #[async_trait] -pub trait ErasedAction: Send + Sync { +pub trait ErasedMethodAction: Send + Sync { fn erased_id(&self) -> String; fn erased_name(&self) -> String; @@ -105,7 +93,7 @@ pub trait ErasedAction: Send + Sync { fn erased_openapi_description(&self) -> &'static str; - fn erased_method(&self) -> ActionMethod; + fn erased_method(&self) -> RequestMethod; fn erased_condition( &self, @@ -129,10 +117,10 @@ pub trait ErasedAction: Send + Sync { } #[macro_export] -macro_rules! erased_action { +macro_rules! erased_method_action { ($action:ident $(, < $( $generic_name:ident : $generic_type:ident ),+ > )*) => { #[async_trait] - impl $( < $( $generic_name: $generic_type + 'static ),+ > )* $crate::ErasedAction for $action $( < $( $generic_name ),+ > )* { + impl $( < $( $generic_name: $generic_type + 'static ),+ > )* $crate::ErasedMethodAction for $action $( < $( $generic_name ),+ > )* { fn erased_id(&self) -> String { self.id() } @@ -149,7 +137,7 @@ macro_rules! erased_action { self.openapi_description() } - fn erased_method(&self) -> $crate::ActionMethod { + fn erased_method(&self) -> $crate::RequestMethod { self.method() } diff --git a/packages/core/shield/src/actions/sign_out.rs b/packages/core/shield/src/actions/sign_out.rs index b058385..f4187d1 100644 --- a/packages/core/shield/src/actions/sign_out.rs +++ b/packages/core/shield/src/actions/sign_out.rs @@ -1,53 +1,61 @@ +use async_trait::async_trait; + use crate::{ + action::Action, error::ShieldError, form::{Form, Input, InputType, InputTypeSubmit, InputValue}, - provider::Provider, - session::MethodSession, + request::{Request, RequestMethod}, + response::{Response, ResponseType}, + session::{BaseSession, SessionAction}, }; const ACTION_ID: &str = "sign-out"; const ACTION_NAME: &str = "Sign out"; -// TODO: Sign out should be a global action that is independent of the method. -// TODO: Add hooks, so the method can still perform custom sign out. - pub struct SignOutAction; -impl SignOutAction { - pub fn id() -> String { - ACTION_ID.to_owned() +#[async_trait] +impl Action for SignOutAction { + fn id(&self) -> &'static str { + ACTION_ID + } + + fn name(&self) -> &'static str { + ACTION_NAME } - pub fn name() -> String { - ACTION_NAME.to_owned() + fn openapi_summary(&self) -> &'static str { + "Sign out" } - pub fn condition( - provider: &P, - session: &MethodSession, - ) -> Result { - Ok(session - .base - .authentication - .as_ref() - .is_some_and(|authentication| { - authentication.method_id == provider.method_id() - && authentication.provider_id == provider.id() - })) + fn openapi_description(&self) -> &'static str { + "Sign out." } - pub async fn forms(_provider: P) -> Result, ShieldError> { + fn method(&self) -> RequestMethod { + RequestMethod::Post + } + + async fn forms(&self) -> Result, ShieldError> { Ok(vec![Form { inputs: vec![Input { name: "submit".to_owned(), label: None, r#type: InputType::Submit(InputTypeSubmit {}), value: Some(InputValue::String { - value: Self::name(), + value: self.name().to_owned(), }), addon_start: None, addon_end: None, }], }]) } + + async fn call( + &self, + _session: &BaseSession, + _request: Request, + ) -> Result { + Ok(Response::new(ResponseType::Default).session_action(SessionAction::Unauthenticate)) + } } diff --git a/packages/core/shield/src/method.rs b/packages/core/shield/src/method.rs index 46919da..42950cc 100644 --- a/packages/core/shield/src/method.rs +++ b/packages/core/shield/src/method.rs @@ -4,8 +4,8 @@ use async_trait::async_trait; use serde::{Serialize, de::DeserializeOwned}; use crate::{ - ErasedAction, - action::Action, + ErasedMethodAction, + action::MethodAction, error::{SessionError, ShieldError}, provider::Provider, }; @@ -17,12 +17,12 @@ pub trait Method: Send + Sync { fn id(&self) -> String; - fn actions(&self) -> Vec>>; + fn actions(&self) -> Vec>>; fn action_by_id( &self, action_id: &str, - ) -> Option>> { + ) -> Option>> { self.actions() .into_iter() .find(|action| action.id() == action_id) @@ -46,9 +46,9 @@ pub trait Method: Send + Sync { pub trait ErasedMethod: Send + Sync { fn erased_id(&self) -> String; - fn erased_actions(&self) -> Vec>; + fn erased_actions(&self) -> Vec>; - fn erased_action_by_id(&self, action_id: &str) -> Option>; + fn erased_action_by_id(&self, action_id: &str) -> Option>; async fn erased_providers( &self, @@ -74,19 +74,19 @@ macro_rules! erased_method { self.id() } - fn erased_actions(&self) -> Vec> { + fn erased_actions(&self) -> Vec> { self.actions() .into_iter() - .map(|action| action as Box) + .map(|action| action as Box) .collect() } fn erased_action_by_id( &self, action_id: &str, - ) -> Option> { + ) -> Option> { self.action_by_id(action_id) - .map(|action| action as Box) + .map(|action| action as Box) } async fn erased_providers( diff --git a/packages/core/shield/src/path.rs b/packages/core/shield/src/path.rs index 2d14c0b..29b184e 100644 --- a/packages/core/shield/src/path.rs +++ b/packages/core/shield/src/path.rs @@ -12,10 +12,18 @@ pub struct ActionFormsPathParams { #[cfg_attr(feature = "utoipa", derive(utoipa::IntoParams))] #[serde(rename_all = "camelCase")] pub struct ActionPathParams { - /// ID of the method. - pub method_id: String, /// ID of the action. pub action_id: String, +} + +#[derive(Deserialize)] +#[cfg_attr(feature = "utoipa", derive(utoipa::IntoParams))] +#[serde(rename_all = "camelCase")] +pub struct MethodActionPathParams { + /// ID of the action. + pub action_id: String, + /// ID of the method. + pub method_id: String, /// ID of provider (optional). pub provider_id: Option, } diff --git a/packages/core/shield/src/request.rs b/packages/core/shield/src/request.rs index f046eda..c1db815 100644 --- a/packages/core/shield/src/request.rs +++ b/packages/core/shield/src/request.rs @@ -1,4 +1,34 @@ use serde_json::Value; +#[cfg(feature = "utoipa")] +use utoipa::openapi::HttpMethod; + +#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub enum RequestMethod { + Get, + Post, + Put, + Delete, + Options, + Head, + Patch, + Trace, +} + +#[cfg(feature = "utoipa")] +impl From for HttpMethod { + fn from(value: RequestMethod) -> Self { + match value { + RequestMethod::Get => Self::Get, + RequestMethod::Post => Self::Post, + RequestMethod::Put => Self::Put, + RequestMethod::Delete => Self::Delete, + RequestMethod::Options => Self::Options, + RequestMethod::Head => Self::Head, + RequestMethod::Patch => Self::Patch, + RequestMethod::Trace => Self::Trace, + } + } +} #[derive(Clone, Debug)] pub struct Request { diff --git a/packages/core/shield/src/session.rs b/packages/core/shield/src/session.rs index 4701466..f0ed4ff 100644 --- a/packages/core/shield/src/session.rs +++ b/packages/core/shield/src/session.rs @@ -6,7 +6,7 @@ use std::{ use async_trait::async_trait; use serde::{Deserialize, Serialize, de::DeserializeOwned}; -use crate::{error::SessionError, user::User}; +use crate::{error::SessionError, provider::Provider, user::User}; #[async_trait] pub trait SessionStorage: Send + Sync { @@ -105,35 +105,51 @@ pub struct Authentication { #[derive(Clone, Debug)] pub enum SessionAction { - Authenticate { user_id: String }, + Authenticate { + method_id: String, + provider_id: Option, + user_id: String, + }, Unauthenticate, - Data(String), + MethodData { + method_id: String, + value: String, + }, } impl SessionAction { - pub fn authenticate(user: U) -> Self { - Self::Authenticate { user_id: user.id() } + pub fn authenticate(provider: &dyn Provider, user: U) -> Self { + Self::Authenticate { + method_id: provider.method_id(), + provider_id: provider.id(), + user_id: user.id(), + } } pub fn unauthenticate() -> Self { Self::Unauthenticate } - pub fn data(value: T) -> Result { + pub fn method_data( + provider: &dyn Provider, + value: T, + ) -> Result { let value = serde_json::to_string(&value) .map_err(|err| SessionError::Serialization(err.to_string()))?; - Ok(Self::Data(value)) + Ok(Self::MethodData { + method_id: provider.method_id(), + value, + }) } - pub(crate) async fn call( - &self, - method_id: &str, - provider_id: Option<&str>, - session: &Session, - ) -> Result<(), SessionError> { + pub(crate) async fn call(&self, session: &Session) -> Result<(), SessionError> { match self { - Self::Authenticate { user_id } => { + Self::Authenticate { + method_id, + provider_id, + user_id, + } => { session.renew().await?; { @@ -143,8 +159,8 @@ impl SessionAction { .map_err(|err| SessionError::Lock(err.to_string()))?; session_data.base.authentication = Some(Authentication { - method_id: method_id.to_owned(), - provider_id: provider_id.map(ToOwned::to_owned), + method_id: method_id.clone(), + provider_id: provider_id.clone(), user_id: user_id.clone(), }); } @@ -154,7 +170,7 @@ impl SessionAction { Self::Unauthenticate => { session.purge().await?; } - Self::Data(value) => { + Self::MethodData { method_id, value } => { { let session_data = session.data(); let mut session_data = session_data diff --git a/packages/core/shield/src/shield.rs b/packages/core/shield/src/shield.rs index 56bd16b..503e6e3 100644 --- a/packages/core/shield/src/shield.rs +++ b/packages/core/shield/src/shield.rs @@ -2,7 +2,7 @@ use std::{any::Any, collections::HashMap, sync::Arc}; #[cfg(feature = "utoipa")] use convert_case::{Case, Casing}; -use futures::future::try_join_all; +use ordered_hash_map::OrderedHashMap; use tracing::warn; #[cfg(feature = "utoipa")] use utoipa::{ @@ -14,9 +14,10 @@ use utoipa::{ }; #[cfg(feature = "utoipa")] -use crate::path::ActionPathParams; +use crate::path::{ActionPathParams, MethodActionPathParams}; use crate::{ - action::{ActionForms, ActionMethodForm, ActionProviderForm}, + SignOutAction, + action::{Action, ActionForms, ActionMethodForm, ActionProviderForm}, error::{ActionError, MethodError, ProviderError, SessionError, ShieldError}, method::ErasedMethod, options::ShieldOptions, @@ -30,7 +31,8 @@ use crate::{ #[derive(Clone)] pub struct Shield { storage: Arc>, - methods: Arc>>, + actions: Arc>>, + methods: Arc>>, options: ShieldOptions, } @@ -39,10 +41,18 @@ impl Shield { where S: Storage + 'static, { - // TOOD: Check for duplicate method IDs. + let actions: [Arc; 1] = [Arc::new(SignOutAction)]; + + // TOOD: Check for duplicate action and method IDs. Self { storage: Arc::new(storage), + actions: Arc::new( + actions + .into_iter() + .map(|action| (action.id().to_owned(), action)) + .collect(), + ), methods: Arc::new( methods .into_iter() @@ -61,23 +71,12 @@ impl Shield { &self.options } - pub fn method_by_id(&self, method_id: &str) -> Option<&dyn ErasedMethod> { - self.methods.get(method_id).map(|v| &**v) + pub fn action_by_id(&self, action_id: &str) -> Option<&dyn Action> { + self.actions.get(action_id).map(|v| &**v) } - pub async fn providers(&self) -> Result>, ShieldError> { - try_join_all( - self.methods - .values() - .map(|provider| provider.erased_providers()), - ) - .await - .map(|providers| { - providers - .into_iter() - .flat_map(|providers| providers.into_iter().map(|(_, provider)| provider)) - .collect::>() - }) + pub fn method_by_id(&self, method_id: &str) -> Option<&dyn ErasedMethod> { + self.methods.get(method_id).map(|v| &**v) } pub async fn provider_by_id( @@ -97,8 +96,14 @@ impl Shield { session: Session, ) -> Result { let mut action_name = None::; + let mut forms = vec![]; let mut method_forms = vec![]; + if let Some(action) = self.actions.get(action_id) { + action_name = Some(action.name().to_owned()); + forms = action.forms().await?; + } + for (method_id, method) in self.methods.iter() { let Some(action) = method.erased_action_by_id(action_id) else { continue; @@ -147,16 +152,45 @@ impl Shield { }); } - method_forms.sort_by(|a, b| a.id.cmp(&b.id)); - Ok(ActionForms { id: action_id.to_owned(), name: action_name.unwrap_or(action_id.to_owned()), + forms, method_forms, }) } pub async fn call( + &self, + action_id: &str, + session: Session, + request: Request, + ) -> Result { + let action = + self.action_by_id(action_id) + .ok_or(ShieldError::Action(ActionError::NotFound( + action_id.to_owned(), + )))?; + + let base_session = { + let session_data = session.data(); + let session_data = session_data + .lock() + .map_err(|err| SessionError::Lock(err.to_string()))?; + + session_data.base.clone() + }; + + let response = action.call(&base_session, request).await?; + + for session_action in &response.session_actions { + session_action.call(&session).await?; + } + + Ok(response.r#type) + } + + pub async fn call_method( &self, action_id: &str, method_id: &str, @@ -201,9 +235,7 @@ impl Shield { .await?; for session_action in &response.session_actions { - session_action - .call(method_id, provider_id, &session) - .await?; + session_action.call(&session).await?; } Ok(response.r#type) @@ -247,19 +279,46 @@ impl Shield { #[cfg(feature = "utoipa")] pub fn openapi(&self) -> OpenApi { + use utoipa::openapi::Response; + let mut paths = Paths::builder(); + for action in self.actions.values() { + let action_id = action.id(); + + // TODO: Query, request body, responses. + + paths = paths.path( + format!("/{action_id}"), + PathItem::builder() + .operation( + action.method().into(), + Operation::builder() + .operation_id(Some(action_id.to_case(Case::Camel))) + .summary(Some(action.openapi_summary())) + .description(Some(action.openapi_description())) + .tag("auth") + .parameters(Some(ActionPathParams::into_params(|| { + Some(ParameterIn::Path) + }))) + .response( + "500", + Response::builder().description("Internal server error."), + ), + ) + .build(), + ); + } + for method in self.methods.values() { for action in method.erased_actions() { - use utoipa::openapi::Response; - let method_id = method.erased_id(); let action_id = action.erased_id(); // TODO: Query, request body, responses. paths = paths.path( - format!("/{}/{}/{{providerId}}", method_id, action_id), + format!("/{action_id}/{method_id}/{{providerId}}"), PathItem::builder() .operation( action.erased_method().into(), @@ -272,7 +331,7 @@ impl Shield { .summary(Some(action.erased_openapi_summary())) .description(Some(action.erased_openapi_description())) .tag("auth") - .parameters(Some(ActionPathParams::into_params(|| { + .parameters(Some(MethodActionPathParams::into_params(|| { Some(ParameterIn::Path) }))) .response( diff --git a/packages/core/shield/src/shield_dyn.rs b/packages/core/shield/src/shield_dyn.rs index 42fb468..c22ad70 100644 --- a/packages/core/shield/src/shield_dyn.rs +++ b/packages/core/shield/src/shield_dyn.rs @@ -1,4 +1,4 @@ -use std::{any::Any, sync::Arc}; +use std::sync::Arc; use async_trait::async_trait; @@ -9,8 +9,6 @@ use crate::{ #[async_trait] pub trait DynShield: Send + Sync { - async fn providers(&self) -> Result>, ShieldError>; - async fn action_forms( &self, action_id: &str, @@ -18,6 +16,13 @@ pub trait DynShield: Send + Sync { ) -> Result; async fn call( + &self, + action_id: &str, + session: Session, + request: Request, + ) -> Result; + + async fn call_method( &self, action_id: &str, method_id: &str, @@ -29,10 +34,6 @@ pub trait DynShield: Send + Sync { #[async_trait] impl DynShield for Shield { - async fn providers(&self) -> Result>, ShieldError> { - self.providers().await - } - async fn action_forms( &self, action_id: &str, @@ -42,6 +43,15 @@ impl DynShield for Shield { } async fn call( + &self, + action_id: &str, + session: Session, + request: Request, + ) -> Result { + self.call(action_id, session, request).await + } + + async fn call_method( &self, action_id: &str, method_id: &str, @@ -49,7 +59,7 @@ impl DynShield for Shield { session: Session, request: Request, ) -> Result { - self.call(action_id, method_id, provider_id, session, request) + self.call_method(action_id, method_id, provider_id, session, request) .await } } @@ -61,10 +71,6 @@ impl ShieldDyn { Self(Arc::new(shield)) } - pub async fn providers(&self) -> Result>, ShieldError> { - self.0.providers().await - } - pub async fn action_forms( &self, action_id: &str, @@ -74,6 +80,15 @@ impl ShieldDyn { } pub async fn call( + &self, + action_id: &str, + session: Session, + request: Request, + ) -> Result { + self.0.call(action_id, session, request).await + } + + pub async fn call_method( &self, action_id: &str, method_id: &str, @@ -82,7 +97,7 @@ impl ShieldDyn { request: Request, ) -> Result { self.0 - .call(action_id, method_id, provider_id, session, request) + .call_method(action_id, method_id, provider_id, session, request) .await } } diff --git a/packages/integrations/shield-axum/src/router.rs b/packages/integrations/shield-axum/src/router.rs index 3612f00..5dc764c 100644 --- a/packages/integrations/shield-axum/src/router.rs +++ b/packages/integrations/shield-axum/src/router.rs @@ -31,8 +31,12 @@ impl AuthRoutes { Router::new() .route("/user", get(user::)) .route("/forms/{actionId}", get(forms::)) - .route("/{methodId}/{actionId}", any(action::)) - .route("/{methodId}/{actionId}/{providerId}", any(action::)) + .route("/{actionId}", any(action::)) + .route("/{actionId}/{methodId}", any(method_action::)) + .route( + "/{actionId}/{methodId}/{providerId}", + any(method_action::), + ) } #[cfg(feature = "utoipa")] @@ -40,9 +44,17 @@ impl AuthRoutes { OpenApiRouter::with_openapi(BaseOpenApi::openapi().merge_from(self.shield.openapi())) .route("/user", get(user::)) .route("/forms/{actionId}", get(forms::)) - .route("/{methodId}/{actionId}", get(action::)) - .route("/{methodId}/{actionId}", post(action::)) - .route("/{methodId}/{actionId}/{providerId}", get(action::)) - .route("/{methodId}/{actionId}/{providerId}", post(action::)) + .route("/{actionId}", get(action::)) + .route("/{actionId}", post(action::)) + .route("/{actionId}/{methodId}", get(method_action::)) + .route("/{actionId}/{methodId}", post(method_action::)) + .route( + "/{actionId}/{methodId}/{providerId}", + get(method_action::), + ) + .route( + "/{actionId}/{methodId}/{providerId}", + post(method_action::), + ) } } diff --git a/packages/integrations/shield-axum/src/routes/action.rs b/packages/integrations/shield-axum/src/routes/action.rs index 65bf75b..8b23e4f 100644 --- a/packages/integrations/shield-axum/src/routes/action.rs +++ b/packages/integrations/shield-axum/src/routes/action.rs @@ -5,7 +5,7 @@ use axum::{ response::{IntoResponse, Redirect, Response}, }; use serde_json::Value; -use shield::{ActionPathParams, Request, ResponseType, User}; +use shield::{ActionPathParams, MethodActionPathParams, Request, ResponseType, User}; #[cfg(feature = "utoipa")] use crate::error::ErrorBody; @@ -16,7 +16,7 @@ use crate::{ExtractSession, ExtractShield, RouteError}; utoipa::path( get, post, - path = "/{methodId}/{actionId}/{providerId}", + path = "/{actionId}", operation_id = "callAction", summary = "Call action", description = "Call an action.", @@ -32,12 +32,55 @@ use crate::{ExtractSession, ExtractShield, RouteError}; ) )] pub async fn action( - Path(ActionPathParams { - method_id, + Path(ActionPathParams { action_id }): Path, + ExtractShield(shield): ExtractShield, + ExtractSession(session): ExtractSession, + Query(query): Query, + Form(form_data): Form, +) -> Result { + // TODO: Check if this action supports the HTTP method (GET/POST)? + + let response = shield + .call(&action_id, session, Request { query, form_data }) + .await?; + + Ok(match response { + ResponseType::Default => StatusCode::NO_CONTENT.into_response(), + ResponseType::Redirect(to) => Redirect::to(&to).into_response(), + ResponseType::RedirectToAction { action_id } => { + // TODO: Use actual frontend prefix instead of hardcoded `/auth`. + Redirect::to(&format!("/auth/{action_id}")).into_response() + } + }) +} + +#[cfg_attr( + feature = "utoipa", + utoipa::path( + get, + post, + path = "/{actionId}/{methodId}/{providerId}", + operation_id = "callMethodAction", + summary = "Call method action", + description = "Call a method action.", + tags = ["auth"], + params( + MethodActionPathParams + ), + responses( + (status = NO_CONTENT, description = "Success."), + (status = SEE_OTHER, description = "Redirect."), + (status = INTERNAL_SERVER_ERROR, description = "Internal server error.", body = ErrorBody), + ) + ) +)] +pub async fn method_action( + Path(MethodActionPathParams { action_id, + method_id, provider_id, .. - }): Path, + }): Path, ExtractShield(shield): ExtractShield, ExtractSession(session): ExtractSession, Query(query): Query, @@ -46,7 +89,7 @@ pub async fn action( // TODO: Check if this action supports the HTTP method (GET/POST)? let response = shield - .call( + .call_method( &action_id, &method_id, provider_id.as_deref(), diff --git a/packages/integrations/shield-dioxus/src/lib.rs b/packages/integrations/shield-dioxus/src/lib.rs index 6ce5936..4e60031 100644 --- a/packages/integrations/shield-dioxus/src/lib.rs +++ b/packages/integrations/shield-dioxus/src/lib.rs @@ -7,5 +7,5 @@ mod style; pub use integration::*; pub use query::*; pub use router::*; -pub use routes::call; +pub use routes::{call, call_method}; pub use style::*; diff --git a/packages/integrations/shield-dioxus/src/routes/action.rs b/packages/integrations/shield-dioxus/src/routes/action.rs index 5b043d5..4d55585 100644 --- a/packages/integrations/shield-dioxus/src/routes/action.rs +++ b/packages/integrations/shield-dioxus/src/routes/action.rs @@ -55,8 +55,6 @@ async fn forms(action_id: String) -> Result { #[post("/api/auth/call", parts: dioxus::fullstack::http::request::Parts)] pub async fn call( action_id: String, - method_id: String, - provider_id: Option, // TODO: Would be nice if this argument could fill up with all unknown keys instead of setting name to `data[...]`. data: Value, ) -> Result { @@ -77,6 +75,44 @@ pub async fn call( let response = shield .call( + &action_id, + session, + Request { + query: Value::Null, + form_data: data, + }, + ) + .await + .context("Failed to call Shield action.")?; + + Ok(response) +} + +#[post("/api/auth/call-method", parts: dioxus::fullstack::http::request::Parts)] +pub async fn call_method( + action_id: String, + method_id: String, + provider_id: Option, + // TODO: Would be nice if this argument could fill up with all unknown keys instead of setting name to `data[...]`. + data: Value, +) -> Result { + use anyhow::anyhow; + use serde_json::Value; + use shield::Request; + + use crate::integration::DioxusIntegrationDyn; + + tracing::info!("call method data {data:#?}"); + + let integration = parts + .extensions + .get::() + .ok_or_else(|| anyhow!("Dioxus Shield integration should be extracted."))?; + let shield = integration.extract_shield(&parts.extensions)?; + let session = integration.extract_session(&parts.extensions)?; + + let response = shield + .call_method( &action_id, &method_id, provider_id.as_deref(), @@ -87,7 +123,7 @@ pub async fn call( }, ) .await - .context("Failed to call Shield action.")?; + .context("Failed to call Shield method action.")?; Ok(response) } diff --git a/packages/integrations/shield-leptos/src/routes/action.rs b/packages/integrations/shield-leptos/src/routes/action.rs index c26b32a..34f4d54 100644 --- a/packages/integrations/shield-leptos/src/routes/action.rs +++ b/packages/integrations/shield-leptos/src/routes/action.rs @@ -51,8 +51,6 @@ async fn forms(action_id: String) -> Result { #[server] pub async fn call( action_id: String, - method_id: String, - provider_id: Option, // TODO: Would be nice if this argument could fill up with all unknown keys instead of setting name to `data[...]`. data: Value, ) -> Result<(), ServerFnError> { @@ -69,6 +67,50 @@ pub async fn call( let response = shield .call( + &action_id, + session, + Request { + query: Value::Null, + form_data: data, + }, + ) + .await?; + + match response { + ResponseType::Default => todo!("default reponse"), + ResponseType::Redirect(to) => { + integration.redirect(&to); + } + ResponseType::RedirectToAction { action_id } => { + // TODO: Use actual router prefix instead of hardcoded `/auth`. + integration.redirect(&format!("/auth/{action_id}")); + } + } + + Ok(()) +} + +#[server] +pub async fn call_method( + action_id: String, + method_id: String, + provider_id: Option, + // TODO: Would be nice if this argument could fill up with all unknown keys instead of setting name to `data[...]`. + data: Value, +) -> Result<(), ServerFnError> { + use serde_json::Value; + use shield::{Request, ResponseType}; + + use crate::expect_server_integration; + + let integration = expect_server_integration(); + let shield = integration.extract_shield().await; + let session = integration.extract_session().await; + + tracing::info!("call method data {data:#?}"); + + let response = shield + .call_method( &action_id, &method_id, provider_id.as_deref(), diff --git a/packages/methods/shield-credentials/src/actions.rs b/packages/methods/shield-credentials/src/actions.rs index 757615c..82b597f 100644 --- a/packages/methods/shield-credentials/src/actions.rs +++ b/packages/methods/shield-credentials/src/actions.rs @@ -1,5 +1,3 @@ mod sign_in; -mod sign_out; pub use sign_in::*; -pub use sign_out::*; diff --git a/packages/methods/shield-credentials/src/actions/sign_in.rs b/packages/methods/shield-credentials/src/actions/sign_in.rs index 7d5339a..83afa63 100644 --- a/packages/methods/shield-credentials/src/actions/sign_in.rs +++ b/packages/methods/shield-credentials/src/actions/sign_in.rs @@ -3,8 +3,8 @@ use std::sync::Arc; use async_trait::async_trait; use serde::de::DeserializeOwned; use shield::{ - Action, ActionMethod, Form, MethodSession, Request, Response, ResponseType, SessionAction, - ShieldError, SignInAction, User, erased_action, + Form, MethodAction, MethodSession, Request, RequestMethod, Response, ResponseType, + SessionAction, ShieldError, SignInAction, User, erased_method_action, }; use crate::{credentials::Credentials, provider::CredentialsProvider}; @@ -20,7 +20,7 @@ impl CredentialsSignInAction { } #[async_trait] -impl Action +impl MethodAction for CredentialsSignInAction { fn id(&self) -> String { @@ -39,8 +39,8 @@ impl Action ActionMethod { - ActionMethod::Post + fn method(&self) -> RequestMethod { + RequestMethod::Post } async fn forms(&self, _provider: CredentialsProvider) -> Result, ShieldError> { @@ -49,7 +49,7 @@ impl Action, request: Request, ) -> Result { @@ -58,8 +58,9 @@ impl Action); +erased_method_action!(CredentialsSignInAction, ); diff --git a/packages/methods/shield-credentials/src/actions/sign_out.rs b/packages/methods/shield-credentials/src/actions/sign_out.rs deleted file mode 100644 index a0b9dd3..0000000 --- a/packages/methods/shield-credentials/src/actions/sign_out.rs +++ /dev/null @@ -1,55 +0,0 @@ -use async_trait::async_trait; -use shield::{ - Action, ActionMethod, Form, MethodSession, Request, Response, ResponseType, SessionAction, - ShieldError, SignOutAction, erased_action, -}; - -use crate::provider::CredentialsProvider; - -pub struct CredentialsSignOutAction; - -#[async_trait] -impl Action for CredentialsSignOutAction { - fn id(&self) -> String { - SignOutAction::id() - } - - fn name(&self) -> String { - SignOutAction::name() - } - - fn openapi_summary(&self) -> &'static str { - "Sign out with credentials" - } - - fn openapi_description(&self) -> &'static str { - "Sign out with credentials." - } - - fn method(&self) -> ActionMethod { - ActionMethod::Post - } - - fn condition( - &self, - provider: &CredentialsProvider, - session: &MethodSession<()>, - ) -> Result { - SignOutAction::condition(provider, session) - } - - async fn forms(&self, provider: CredentialsProvider) -> Result, ShieldError> { - SignOutAction::forms(provider).await - } - - async fn call( - &self, - _provider: CredentialsProvider, - _session: &MethodSession<()>, - _request: Request, - ) -> Result { - Ok(Response::new(ResponseType::Default).session_action(SessionAction::Unauthenticate)) - } -} - -erased_action!(CredentialsSignOutAction); diff --git a/packages/methods/shield-credentials/src/method.rs b/packages/methods/shield-credentials/src/method.rs index e48c5cf..ebb0a66 100644 --- a/packages/methods/shield-credentials/src/method.rs +++ b/packages/methods/shield-credentials/src/method.rs @@ -2,12 +2,10 @@ use std::sync::Arc; use async_trait::async_trait; use serde::de::DeserializeOwned; -use shield::{Action, Method, ShieldError, User, erased_method}; +use shield::{Method, MethodAction, ShieldError, User, erased_method}; use crate::{ - actions::{CredentialsSignInAction, CredentialsSignOutAction}, - credentials::Credentials, - provider::CredentialsProvider, + actions::CredentialsSignInAction, credentials::Credentials, provider::CredentialsProvider, }; pub const CREDENTIALS_METHOD_ID: &str = "credentials"; @@ -33,11 +31,10 @@ impl Method for CredentialsMet CREDENTIALS_METHOD_ID.to_owned() } - fn actions(&self) -> Vec>> { - vec![ - Box::new(CredentialsSignInAction::new(self.credentials.clone())), - Box::new(CredentialsSignOutAction), - ] + fn actions(&self) -> Vec>> { + vec![Box::new(CredentialsSignInAction::new( + self.credentials.clone(), + ))] } async fn providers(&self) -> Result, ShieldError> { diff --git a/packages/methods/shield-dummy/src/actions.rs b/packages/methods/shield-dummy/src/actions.rs index 757615c..82b597f 100644 --- a/packages/methods/shield-dummy/src/actions.rs +++ b/packages/methods/shield-dummy/src/actions.rs @@ -1,5 +1,3 @@ mod sign_in; -mod sign_out; pub use sign_in::*; -pub use sign_out::*; diff --git a/packages/methods/shield-dummy/src/actions/sign_in.rs b/packages/methods/shield-dummy/src/actions/sign_in.rs index 2512e9d..75889ac 100644 --- a/packages/methods/shield-dummy/src/actions/sign_in.rs +++ b/packages/methods/shield-dummy/src/actions/sign_in.rs @@ -3,9 +3,9 @@ use std::sync::Arc; use async_trait::async_trait; use serde::Deserialize; use shield::{ - Action, ActionMethod, Form, Input, InputType, InputTypeSubmit, InputTypeText, InputValue, - MethodSession, Request, Response, ResponseType, SessionAction, ShieldError, SignInAction, - Storage, User, erased_action, + Form, Input, InputType, InputTypeSubmit, InputTypeText, InputValue, MethodAction, + MethodSession, Request, RequestMethod, Response, ResponseType, SessionAction, ShieldError, + SignInAction, Storage, User, erased_method_action, }; use crate::provider::DummyProvider; @@ -27,7 +27,7 @@ impl DummySignInAction { } #[async_trait] -impl Action for DummySignInAction { +impl MethodAction for DummySignInAction { fn id(&self) -> String { SignInAction::id() } @@ -44,8 +44,8 @@ impl Action for DummySignInAction { "Sign in with dummy." } - fn method(&self) -> ActionMethod { - ActionMethod::Post + fn method(&self) -> RequestMethod { + RequestMethod::Post } async fn forms(&self, _provider: DummyProvider) -> Result, ShieldError> { @@ -79,7 +79,7 @@ impl Action for DummySignInAction { async fn call( &self, - _provider: DummyProvider, + provider: DummyProvider, _session: &MethodSession<()>, request: Request, ) -> Result { @@ -92,8 +92,9 @@ impl Action for DummySignInAction { .await? .ok_or_else(|| ShieldError::Validation("User not found.".to_owned()))?; - Ok(Response::new(ResponseType::Default).session_action(SessionAction::authenticate(user))) + Ok(Response::new(ResponseType::Default) + .session_action(SessionAction::authenticate(&provider, user))) } } -erased_action!(DummySignInAction, ); +erased_method_action!(DummySignInAction, ); diff --git a/packages/methods/shield-dummy/src/actions/sign_out.rs b/packages/methods/shield-dummy/src/actions/sign_out.rs deleted file mode 100644 index b1ae478..0000000 --- a/packages/methods/shield-dummy/src/actions/sign_out.rs +++ /dev/null @@ -1,55 +0,0 @@ -use async_trait::async_trait; -use shield::{ - Action, ActionMethod, Form, MethodSession, Request, Response, ResponseType, SessionAction, - ShieldError, SignOutAction, erased_action, -}; - -use crate::provider::DummyProvider; - -pub struct DummySignOutAction; - -#[async_trait] -impl Action for DummySignOutAction { - fn id(&self) -> String { - SignOutAction::id() - } - - fn name(&self) -> String { - SignOutAction::name() - } - - fn openapi_summary(&self) -> &'static str { - "Sign out with dummy" - } - - fn openapi_description(&self) -> &'static str { - "Sign out with dummy." - } - - fn method(&self) -> ActionMethod { - ActionMethod::Post - } - - fn condition( - &self, - provider: &DummyProvider, - session: &MethodSession<()>, - ) -> Result { - SignOutAction::condition(provider, session) - } - - async fn forms(&self, provider: DummyProvider) -> Result, ShieldError> { - SignOutAction::forms(provider).await - } - - async fn call( - &self, - _provider: DummyProvider, - _session: &MethodSession<()>, - _request: Request, - ) -> Result { - Ok(Response::new(ResponseType::Default).session_action(SessionAction::Unauthenticate)) - } -} - -erased_action!(DummySignOutAction); diff --git a/packages/methods/shield-dummy/src/method.rs b/packages/methods/shield-dummy/src/method.rs index 50cf812..9c5b068 100644 --- a/packages/methods/shield-dummy/src/method.rs +++ b/packages/methods/shield-dummy/src/method.rs @@ -1,12 +1,9 @@ use std::sync::Arc; use async_trait::async_trait; -use shield::{Action, Method, ShieldError, Storage, User, erased_method}; +use shield::{Method, MethodAction, ShieldError, Storage, User, erased_method}; -use crate::{ - actions::{DummySignInAction, DummySignOutAction}, - provider::DummyProvider, -}; +use crate::{actions::DummySignInAction, provider::DummyProvider}; pub const DUMMY_METHOD_ID: &str = "dummy"; @@ -31,11 +28,8 @@ impl Method for DummyMethod { DUMMY_METHOD_ID.to_owned() } - fn actions(&self) -> Vec>> { - vec![ - Box::new(DummySignInAction::new(self.storage.clone())), - Box::new(DummySignOutAction), - ] + fn actions(&self) -> Vec>> { + vec![Box::new(DummySignInAction::new(self.storage.clone()))] } async fn providers(&self) -> Result, ShieldError> { diff --git a/packages/methods/shield-email/src/actions.rs b/packages/methods/shield-email/src/actions.rs index 47d587d..5a50000 100644 --- a/packages/methods/shield-email/src/actions.rs +++ b/packages/methods/shield-email/src/actions.rs @@ -1,7 +1,5 @@ mod sign_in; mod sign_in_callback; -mod sign_out; pub use sign_in::*; pub use sign_in_callback::*; -pub use sign_out::*; diff --git a/packages/methods/shield-email/src/actions/sign_in.rs b/packages/methods/shield-email/src/actions/sign_in.rs index a129399..ce9aa87 100644 --- a/packages/methods/shield-email/src/actions/sign_in.rs +++ b/packages/methods/shield-email/src/actions/sign_in.rs @@ -5,9 +5,9 @@ use chrono::Utc; use rand::distr::{Alphanumeric, SampleString}; use serde::Deserialize; use shield::{ - Action, ActionMethod, Form, Input, InputType, InputTypeEmail, InputTypeSubmit, InputValue, - MethodSession, Request, Response, ResponseType, SessionAction, ShieldError, SignInAction, User, - erased_action, + Form, Input, InputType, InputTypeEmail, InputTypeSubmit, InputValue, MethodAction, + MethodSession, Request, RequestMethod, Response, ResponseType, SessionAction, ShieldError, + SignInAction, User, erased_method_action, }; use crate::{ @@ -36,7 +36,7 @@ impl EmailSignInAction { } #[async_trait] -impl Action for EmailSignInAction { +impl MethodAction for EmailSignInAction { fn id(&self) -> String { SignInAction::id() } @@ -53,8 +53,8 @@ impl Action for EmailSignInAction { "Sign in with email." } - fn method(&self) -> ActionMethod { - ActionMethod::Post + fn method(&self) -> RequestMethod { + RequestMethod::Post } async fn forms(&self, _provider: EmailProvider) -> Result, ShieldError> { @@ -116,4 +116,4 @@ impl Action for EmailSignInAction { } } -erased_action!(EmailSignInAction, ); +erased_method_action!(EmailSignInAction, ); diff --git a/packages/methods/shield-email/src/actions/sign_in_callback.rs b/packages/methods/shield-email/src/actions/sign_in_callback.rs index b20ae7e..f476577 100644 --- a/packages/methods/shield-email/src/actions/sign_in_callback.rs +++ b/packages/methods/shield-email/src/actions/sign_in_callback.rs @@ -4,9 +4,9 @@ use async_trait::async_trait; use chrono::Utc; use serde::Deserialize; use shield::{ - Action, ActionMethod, CreateEmailAddress, CreateUser, Form, Input, InputType, InputTypeEmail, - InputTypeSubmit, InputTypeText, InputValue, MethodSession, Request, Response, ResponseType, - SessionAction, ShieldError, SignInCallbackAction, User, erased_action, + CreateEmailAddress, CreateUser, Form, Input, InputType, InputTypeEmail, InputTypeSubmit, + InputTypeText, InputValue, MethodAction, MethodSession, Request, RequestMethod, Response, + ResponseType, SessionAction, ShieldError, SignInCallbackAction, User, erased_method_action, }; use crate::{ @@ -33,7 +33,7 @@ impl EmailSignInCallbackAction { } #[async_trait] -impl Action for EmailSignInCallbackAction { +impl MethodAction for EmailSignInCallbackAction { fn id(&self) -> String { SignInCallbackAction::id() } @@ -50,8 +50,8 @@ impl Action for EmailSignInCallbackAction< "Sign in callback for email." } - fn method(&self) -> ActionMethod { - ActionMethod::Post + fn method(&self) -> RequestMethod { + RequestMethod::Post } fn condition( @@ -110,7 +110,7 @@ impl Action for EmailSignInCallbackAction< async fn call( &self, - _provider: EmailProvider, + provider: EmailProvider, _session: &MethodSession<()>, request: Request, ) -> Result { @@ -154,8 +154,8 @@ impl Action for EmailSignInCallbackAction< Ok(Response::new(ResponseType::Redirect( self.options.sign_in_redirect.clone(), )) - .session_action(SessionAction::authenticate(user))) + .session_action(SessionAction::authenticate(&provider, user))) } } -erased_action!(EmailSignInCallbackAction, ); +erased_method_action!(EmailSignInCallbackAction, ); diff --git a/packages/methods/shield-email/src/actions/sign_out.rs b/packages/methods/shield-email/src/actions/sign_out.rs deleted file mode 100644 index 899490b..0000000 --- a/packages/methods/shield-email/src/actions/sign_out.rs +++ /dev/null @@ -1,55 +0,0 @@ -use async_trait::async_trait; -use shield::{ - Action, ActionMethod, Form, MethodSession, Request, Response, ResponseType, SessionAction, - ShieldError, SignOutAction, erased_action, -}; - -use crate::provider::EmailProvider; - -pub struct EmailSignOutAction; - -#[async_trait] -impl Action for EmailSignOutAction { - fn id(&self) -> String { - SignOutAction::id() - } - - fn name(&self) -> String { - SignOutAction::name() - } - - fn openapi_summary(&self) -> &'static str { - "Sign out with OpenID Connect" - } - - fn openapi_description(&self) -> &'static str { - "Sign out with OpenID Connect." - } - - fn method(&self) -> ActionMethod { - ActionMethod::Post - } - - fn condition( - &self, - provider: &EmailProvider, - session: &MethodSession<()>, - ) -> Result { - SignOutAction::condition(provider, session) - } - - async fn forms(&self, provider: EmailProvider) -> Result, ShieldError> { - SignOutAction::forms(provider).await - } - - async fn call( - &self, - _provider: EmailProvider, - _session: &MethodSession<()>, - _request: Request, - ) -> Result { - Ok(Response::new(ResponseType::Default).session_action(SessionAction::Unauthenticate)) - } -} - -erased_action!(EmailSignOutAction); diff --git a/packages/methods/shield-email/src/method.rs b/packages/methods/shield-email/src/method.rs index d6fe707..ab50a21 100644 --- a/packages/methods/shield-email/src/method.rs +++ b/packages/methods/shield-email/src/method.rs @@ -1,10 +1,10 @@ use std::sync::Arc; use async_trait::async_trait; -use shield::{Action, Method, ShieldError, User, erased_method}; +use shield::{Method, MethodAction, ShieldError, User, erased_method}; use crate::{ - actions::{EmailSignInAction, EmailSignInCallbackAction, EmailSignOutAction}, + actions::{EmailSignInAction, EmailSignInCallbackAction}, options::EmailOptions, provider::EmailProvider, storage::EmailStorage, @@ -35,7 +35,7 @@ impl Method for EmailMethod { EMAIL_METHOD_ID.to_owned() } - fn actions(&self) -> Vec>> { + fn actions(&self) -> Vec>> { vec![ Box::new(EmailSignInAction::new( self.options.clone(), @@ -45,7 +45,6 @@ impl Method for EmailMethod { self.options.clone(), self.storage.clone(), )), - Box::new(EmailSignOutAction), ] } diff --git a/packages/methods/shield-oauth/Cargo.toml b/packages/methods/shield-oauth/Cargo.toml index df85772..c2c23de 100644 --- a/packages/methods/shield-oauth/Cargo.toml +++ b/packages/methods/shield-oauth/Cargo.toml @@ -12,9 +12,9 @@ version.workspace = true ignored = ["serde_json"] [features] -default = [] +default = ["rustls"] native-tls = ["oauth2/native-tls"] -rustls-tls = ["oauth2/rustls-tls"] +rustls = ["oauth2/rustls-tls"] [dependencies] async-trait.workspace = true diff --git a/packages/methods/shield-oauth/src/actions.rs b/packages/methods/shield-oauth/src/actions.rs index 47d587d..5a50000 100644 --- a/packages/methods/shield-oauth/src/actions.rs +++ b/packages/methods/shield-oauth/src/actions.rs @@ -1,7 +1,5 @@ mod sign_in; mod sign_in_callback; -mod sign_out; pub use sign_in::*; pub use sign_in_callback::*; -pub use sign_out::*; diff --git a/packages/methods/shield-oauth/src/actions/sign_in.rs b/packages/methods/shield-oauth/src/actions/sign_in.rs index bf6ee23..d881af3 100644 --- a/packages/methods/shield-oauth/src/actions/sign_in.rs +++ b/packages/methods/shield-oauth/src/actions/sign_in.rs @@ -2,9 +2,9 @@ use async_trait::async_trait; use oauth2::{CsrfToken, PkceCodeChallenge, Scope, url::form_urlencoded::parse}; use serde::Deserialize; use shield::{ - Action, ActionMethod, ConfigurationError, Form, Input, InputAddon, InputType, InputTypeHidden, - InputTypeSubmit, InputValue, MethodSession, Provider, Request, Response, ResponseType, - SessionAction, ShieldError, SignInAction, erased_action, + ConfigurationError, Form, Input, InputAddon, InputType, InputTypeHidden, InputTypeSubmit, + InputValue, MethodAction, MethodSession, Provider, Request, RequestMethod, Response, + ResponseType, SessionAction, ShieldError, SignInAction, erased_method_action, }; use url::Url; @@ -32,7 +32,7 @@ impl OauthSignInAction { } #[async_trait] -impl Action for OauthSignInAction { +impl MethodAction for OauthSignInAction { fn id(&self) -> String { SignInAction::id() } @@ -49,8 +49,8 @@ impl Action for OauthSignInAction { "Sign in with OAuth." } - fn method(&self) -> ActionMethod { - ActionMethod::Post + fn method(&self) -> RequestMethod { + RequestMethod::Post } async fn forms(&self, provider: OauthProvider) -> Result, ShieldError> { @@ -151,12 +151,12 @@ impl Action for OauthSignInAction { authorization_request.set_pkce_challenge(pkce_code_challenge.clone()); } - if let Some(scopes) = provider.scopes { + if let Some(scopes) = &provider.scopes { authorization_request = - authorization_request.add_scopes(scopes.into_iter().map(Scope::new)); + authorization_request.add_scopes(scopes.iter().cloned().map(Scope::new)); } - if let Some(authorization_url_params) = provider.authorization_url_params { + if let Some(authorization_url_params) = &provider.authorization_url_params { let params = parse(authorization_url_params.trim_start_matches('?').as_bytes()); for (name, value) in params { @@ -169,14 +169,17 @@ impl Action for OauthSignInAction { Ok(Response::new(ResponseType::Redirect(auth_url.to_string())) .session_action(SessionAction::Unauthenticate) - .session_action(SessionAction::data(OauthSession { - redirect_url: Some(redirect_url), - csrf: Some(csrf_token.secret().clone()), - pkce_verifier: pkce_code_challenge - .map(|(_, pkce_code_verifier)| pkce_code_verifier.secret().clone()), - oauth_connection_id: None, - })?)) + .session_action(SessionAction::method_data( + &provider, + OauthSession { + redirect_url: Some(redirect_url), + csrf: Some(csrf_token.secret().clone()), + pkce_verifier: pkce_code_challenge + .map(|(_, pkce_code_verifier)| pkce_code_verifier.secret().clone()), + oauth_connection_id: None, + }, + )?)) } } -erased_action!(OauthSignInAction); +erased_method_action!(OauthSignInAction); diff --git a/packages/methods/shield-oauth/src/actions/sign_in_callback.rs b/packages/methods/shield-oauth/src/actions/sign_in_callback.rs index f03e03e..114c299 100644 --- a/packages/methods/shield-oauth/src/actions/sign_in_callback.rs +++ b/packages/methods/shield-oauth/src/actions/sign_in_callback.rs @@ -8,9 +8,9 @@ use oauth2::{ }; use secrecy::SecretString; use shield::{ - Action, ActionMethod, ConfigurationError, CreateEmailAddress, CreateUser, Form, MethodSession, - Request, Response, ResponseType, SessionAction, ShieldError, SignInCallbackAction, UpdateUser, - User, erased_action, + ConfigurationError, CreateEmailAddress, CreateUser, Form, MethodAction, MethodSession, Request, + RequestMethod, Response, ResponseType, SessionAction, ShieldError, SignInCallbackAction, + UpdateUser, User, erased_method_action, }; use crate::{ @@ -129,7 +129,7 @@ impl OauthSignInCallbackAction { } #[async_trait] -impl Action for OauthSignInCallbackAction { +impl MethodAction for OauthSignInCallbackAction { fn id(&self) -> String { SignInCallbackAction::id() } @@ -146,8 +146,8 @@ impl Action for OauthSignInCallb "Sign in callback for OAuth." } - fn method(&self) -> ActionMethod { - ActionMethod::Get + fn method(&self) -> RequestMethod { + RequestMethod::Get } fn condition( @@ -205,7 +205,7 @@ impl Action for OauthSignInCallb return Err(ShieldError::Validation("Missing PKCE verifier.".to_owned())); } - if let Some(token_url_params) = provider.token_url_params { + if let Some(token_url_params) = &provider.token_url_params { let params = parse(token_url_params.trim_start_matches('?').as_bytes()); for (name, value) in params { @@ -264,17 +264,20 @@ impl Action for OauthSignInCallb .map(ToString::to_string) .unwrap_or_else(|| self.options.sign_in_redirect.clone()), )) - .session_action(SessionAction::authenticate(user)) - .session_action(SessionAction::data(OauthSession { - redirect_url: None, - csrf: None, - pkce_verifier: None, - oauth_connection_id: Some(connection.id), - })?)) + .session_action(SessionAction::authenticate(&provider, user)) + .session_action(SessionAction::method_data( + &provider, + OauthSession { + redirect_url: None, + csrf: None, + pkce_verifier: None, + oauth_connection_id: Some(connection.id), + }, + )?)) } } -erased_action!(OauthSignInCallbackAction, ); +erased_method_action!(OauthSignInCallbackAction, ); type ParsedTokenResponse = ( String, diff --git a/packages/methods/shield-oauth/src/actions/sign_out.rs b/packages/methods/shield-oauth/src/actions/sign_out.rs deleted file mode 100644 index eceb75a..0000000 --- a/packages/methods/shield-oauth/src/actions/sign_out.rs +++ /dev/null @@ -1,57 +0,0 @@ -use async_trait::async_trait; -use shield::{ - Action, ActionMethod, Form, MethodSession, Request, Response, ResponseType, SessionAction, - ShieldError, SignOutAction, erased_action, -}; - -use crate::{provider::OauthProvider, session::OauthSession}; - -pub struct OauthSignOutAction; - -#[async_trait] -impl Action for OauthSignOutAction { - fn id(&self) -> String { - SignOutAction::id() - } - - fn name(&self) -> String { - SignOutAction::name() - } - - fn openapi_summary(&self) -> &'static str { - "Sign out with OAuth" - } - - fn openapi_description(&self) -> &'static str { - "Sign out with OAuth." - } - - fn method(&self) -> ActionMethod { - ActionMethod::Post - } - - fn condition( - &self, - provider: &OauthProvider, - session: &MethodSession, - ) -> Result { - SignOutAction::condition(provider, session) - } - - async fn forms(&self, provider: OauthProvider) -> Result, ShieldError> { - SignOutAction::forms(provider).await - } - - async fn call( - &self, - _provider: OauthProvider, - _session: &MethodSession, - _request: Request, - ) -> Result { - // TODO: OAuth token revocation. - - Ok(Response::new(ResponseType::Default).session_action(SessionAction::Unauthenticate)) - } -} - -erased_action!(OauthSignOutAction); diff --git a/packages/methods/shield-oauth/src/method.rs b/packages/methods/shield-oauth/src/method.rs index 329fda1..8e371a0 100644 --- a/packages/methods/shield-oauth/src/method.rs +++ b/packages/methods/shield-oauth/src/method.rs @@ -1,16 +1,18 @@ use std::sync::Arc; use async_trait::async_trait; -use shield::{Action, Method, ShieldError, User, erased_method}; +use shield::{Method, MethodAction, ShieldError, User, erased_method}; use crate::{ - actions::{OauthSignInAction, OauthSignInCallbackAction, OauthSignOutAction}, + actions::{OauthSignInAction, OauthSignInCallbackAction}, options::OauthOptions, provider::OauthProvider, session::OauthSession, storage::OauthStorage, }; +// TODO: Add sign out hook. + pub const OAUTH_METHOD_ID: &str = "oauth"; pub struct OauthMethod { @@ -71,14 +73,13 @@ impl Method for OauthMethod { OAUTH_METHOD_ID.to_owned() } - fn actions(&self) -> Vec>> { + fn actions(&self) -> Vec>> { vec![ Box::new(OauthSignInAction::new(self.options.clone())), Box::new(OauthSignInCallbackAction::new( self.options.clone(), self.storage.clone(), )), - Box::new(OauthSignOutAction), ] } diff --git a/packages/methods/shield-oidc/Cargo.toml b/packages/methods/shield-oidc/Cargo.toml index cd448ad..c1195a0 100644 --- a/packages/methods/shield-oidc/Cargo.toml +++ b/packages/methods/shield-oidc/Cargo.toml @@ -12,9 +12,9 @@ version.workspace = true ignored = ["oauth2", "serde_json"] [features] -default = [] +default = ["rustls"] native-tls = ["oauth2/native-tls", "openidconnect/native-tls"] -rustls-tls = ["oauth2/rustls-tls", "openidconnect/rustls-tls"] +rustls = ["oauth2/rustls-tls", "openidconnect/rustls-tls"] [dependencies] async-trait.workspace = true diff --git a/packages/methods/shield-oidc/src/actions.rs b/packages/methods/shield-oidc/src/actions.rs index 47d587d..3a4c7f9 100644 --- a/packages/methods/shield-oidc/src/actions.rs +++ b/packages/methods/shield-oidc/src/actions.rs @@ -4,4 +4,3 @@ mod sign_out; pub use sign_in::*; pub use sign_in_callback::*; -pub use sign_out::*; diff --git a/packages/methods/shield-oidc/src/actions/sign_in.rs b/packages/methods/shield-oidc/src/actions/sign_in.rs index 68fccca..2aaf874 100644 --- a/packages/methods/shield-oidc/src/actions/sign_in.rs +++ b/packages/methods/shield-oidc/src/actions/sign_in.rs @@ -5,9 +5,9 @@ use openidconnect::{ }; use serde::Deserialize; use shield::{ - Action, ActionMethod, Form, Input, InputAddon, InputType, InputTypeHidden, InputTypeSubmit, - InputValue, MethodSession, Provider, Request, Response, ResponseType, SessionAction, - ShieldError, SignInAction, erased_action, + Form, Input, InputAddon, InputType, InputTypeHidden, InputTypeSubmit, InputValue, MethodAction, + MethodSession, Provider, Request, RequestMethod, Response, ResponseType, SessionAction, + ShieldError, SignInAction, erased_method_action, }; use url::Url; @@ -35,7 +35,7 @@ impl OidcSignInAction { } #[async_trait] -impl Action for OidcSignInAction { +impl MethodAction for OidcSignInAction { fn id(&self) -> String { SignInAction::id() } @@ -52,8 +52,8 @@ impl Action for OidcSignInAction { "Sign in with OpenID Connect." } - fn method(&self) -> ActionMethod { - ActionMethod::Post + fn method(&self) -> RequestMethod { + RequestMethod::Post } async fn forms(&self, provider: OidcProvider) -> Result, ShieldError> { @@ -156,12 +156,12 @@ impl Action for OidcSignInAction { authorization_request.set_pkce_challenge(pkce_code_challenge.clone()); } - if let Some(scopes) = provider.scopes { + if let Some(scopes) = &provider.scopes { authorization_request = - authorization_request.add_scopes(scopes.into_iter().map(Scope::new)); + authorization_request.add_scopes(scopes.iter().cloned().map(Scope::new)); } - if let Some(authorization_url_params) = provider.authorization_url_params { + if let Some(authorization_url_params) = &provider.authorization_url_params { let params = parse(authorization_url_params.trim_start_matches('?').as_bytes()); for (name, value) in params { @@ -174,15 +174,18 @@ impl Action for OidcSignInAction { Ok(Response::new(ResponseType::Redirect(auth_url.to_string())) .session_action(SessionAction::unauthenticate()) - .session_action(SessionAction::data(OidcSession { - redirect_url: Some(redirect_url), - csrf: Some(csrf_token.secret().clone()), - nonce: Some(nonce.secret().clone()), - pkce_verifier: pkce_code_challenge - .map(|(_, pkce_code_verifier)| pkce_code_verifier.secret().clone()), - oidc_connection_id: None, - })?)) + .session_action(SessionAction::method_data( + &provider, + OidcSession { + redirect_url: Some(redirect_url), + csrf: Some(csrf_token.secret().clone()), + nonce: Some(nonce.secret().clone()), + pkce_verifier: pkce_code_challenge + .map(|(_, pkce_code_verifier)| pkce_code_verifier.secret().clone()), + oidc_connection_id: None, + }, + )?)) } } -erased_action!(OidcSignInAction); +erased_method_action!(OidcSignInAction); diff --git a/packages/methods/shield-oidc/src/actions/sign_in_callback.rs b/packages/methods/shield-oidc/src/actions/sign_in_callback.rs index 9b3b229..9248a62 100644 --- a/packages/methods/shield-oidc/src/actions/sign_in_callback.rs +++ b/packages/methods/shield-oidc/src/actions/sign_in_callback.rs @@ -10,9 +10,9 @@ use openidconnect::{ }; use secrecy::SecretString; use shield::{ - Action, ActionMethod, ConfigurationError, CreateEmailAddress, CreateUser, Form, MethodSession, - Request, Response, ResponseType, SessionAction, ShieldError, SignInCallbackAction, UpdateUser, - User, erased_action, + ConfigurationError, CreateEmailAddress, CreateUser, Form, MethodAction, MethodSession, Request, + RequestMethod, Response, ResponseType, SessionAction, ShieldError, SignInCallbackAction, + UpdateUser, User, erased_method_action, }; use tracing::debug; @@ -140,7 +140,7 @@ impl OidcSignInCallbackAction { } #[async_trait] -impl Action for OidcSignInCallbackAction { +impl MethodAction for OidcSignInCallbackAction { fn id(&self) -> String { SignInCallbackAction::id() } @@ -157,8 +157,8 @@ impl Action for OidcSignInCallback "Sign in callback for OpenID Connect." } - fn method(&self) -> ActionMethod { - ActionMethod::Get + fn method(&self) -> RequestMethod { + RequestMethod::Get } fn condition( @@ -217,7 +217,7 @@ impl Action for OidcSignInCallback return Err(ShieldError::Validation("Missing PKCE verifier.".to_owned())); } - if let Some(token_url_params) = provider.token_url_params { + if let Some(token_url_params) = &provider.token_url_params { let params = parse(token_url_params.trim_start_matches('?').as_bytes()); for (name, value) in params { @@ -298,18 +298,21 @@ impl Action for OidcSignInCallback .map(ToString::to_string) .unwrap_or_else(|| self.options.sign_in_redirect.clone()), )) - .session_action(SessionAction::authenticate(user)) - .session_action(SessionAction::data(OidcSession { - redirect_url: None, - csrf: None, - nonce: None, - pkce_verifier: None, - oidc_connection_id: Some(connection.id), - })?)) + .session_action(SessionAction::authenticate(&provider, user)) + .session_action(SessionAction::method_data( + &provider, + OidcSession { + redirect_url: None, + csrf: None, + nonce: None, + pkce_verifier: None, + oidc_connection_id: Some(connection.id), + }, + )?)) } } -erased_action!(OidcSignInCallbackAction, ); +erased_method_action!(OidcSignInCallbackAction, ); type ParsedTokenResponse = ( String, diff --git a/packages/methods/shield-oidc/src/actions/sign_out.rs b/packages/methods/shield-oidc/src/actions/sign_out.rs index ecb6d4b..01a1d52 100644 --- a/packages/methods/shield-oidc/src/actions/sign_out.rs +++ b/packages/methods/shield-oidc/src/actions/sign_out.rs @@ -1,106 +1,52 @@ -use async_trait::async_trait; -use shield::{ - Action, ActionMethod, Form, MethodSession, Request, Response, ResponseType, SessionAction, - ShieldError, SignOutAction, erased_action, -}; - -use crate::{provider::OidcProvider, session::OidcSession}; - -pub struct OidcSignOutAction; - -#[async_trait] -impl Action for OidcSignOutAction { - fn id(&self) -> String { - SignOutAction::id() - } - - fn name(&self) -> String { - SignOutAction::name() - } - - fn openapi_summary(&self) -> &'static str { - "Sign out with OpenID Connect" - } - - fn openapi_description(&self) -> &'static str { - "Sign out with OpenID Connect." - } - - fn method(&self) -> ActionMethod { - ActionMethod::Post - } - - fn condition( - &self, - provider: &OidcProvider, - session: &MethodSession, - ) -> Result { - SignOutAction::condition(provider, session) - } - - async fn forms(&self, provider: OidcProvider) -> Result, ShieldError> { - SignOutAction::forms(provider).await - } - - async fn call( - &self, - _provider: OidcProvider, - _session: &MethodSession, - _request: Request, - ) -> Result { - // TODO: See [`OidcProvider::oidc_client`]. - - // let provider = match request.provider_id { - // Some(provider_id) => self.oidc_provider_by_id_or_slug(&provider_id).await?, - // None => return Err(ProviderError::ProviderMissing.into()), - // }; - - // let connection_id = { - // let session_data = session.data(); - // let session_data = session_data - // .lock() - // .map_err(|err| SessionError::Lock(err.to_string()))?; - - // session_data.oidc_connection_id.clone() - // }; - - // if let Some(connection_id) = connection_id { - // if let Some(connection) = self.storage.oidc_connection_by_id(&connection_id).await? { - // debug!("revoking access token {:?}", connection.access_token); - - // let token = AccessToken::new(connection.access_token); - - // let client = subprovider.oidc_client().await?; - - // let revocation_request = match client.revoke_token(token.into()) { - // Ok(revocation_request) => Some(revocation_request), - // Err(openidconnect::ConfigurationError::MissingUrl("revocation")) => None, - // Err(err) => return Err(ConfigurationError::Invalid(err.to_string()).into()), - // }; - - // if let Some(revocation_request) = revocation_request { - // let mut revocation_request = revocation_request; - - // if let Some(revocation_url_params) = subprovider.revocation_url_params { - // let params = - // parse(revocation_url_params.trim_start_matches('?').as_bytes()); - - // for (name, value) in params { - // revocation_request = revocation_request - // .add_extra_param(name.into_owned(), value.into_owned()); - // } - // } - - // revocation_request - // .request_async(async_http_client) - // .await - // .map_err(|err| ShieldError::Request(err.to_string()))?; - // } - // } - // } - - Ok(Response::new(ResponseType::Default).session_action(SessionAction::Unauthenticate)) - } -} - -erased_action!(OidcSignOutAction); +// TODO: Add hooks to perform the logic below. + +// TODO: See [`OidcProvider::oidc_client`]. + +// let provider = match request.provider_id { +// Some(provider_id) => self.oidc_provider_by_id_or_slug(&provider_id).await?, +// None => return Err(ProviderError::ProviderMissing.into()), +// }; + +// let connection_id = { +// let session_data = session.data(); +// let session_data = session_data +// .lock() +// .map_err(|err| SessionError::Lock(err.to_string()))?; + +// session_data.oidc_connection_id.clone() +// }; + +// if let Some(connection_id) = connection_id { +// if let Some(connection) = self.storage.oidc_connection_by_id(&connection_id).await? { +// debug!("revoking access token {:?}", connection.access_token); + +// let token = AccessToken::new(connection.access_token); + +// let client = subprovider.oidc_client().await?; + +// let revocation_request = match client.revoke_token(token.into()) { +// Ok(revocation_request) => Some(revocation_request), +// Err(openidconnect::ConfigurationError::MissingUrl("revocation")) => None, +// Err(err) => return Err(ConfigurationError::Invalid(err.to_string()).into()), +// }; + +// if let Some(revocation_request) = revocation_request { +// let mut revocation_request = revocation_request; + +// if let Some(revocation_url_params) = subprovider.revocation_url_params { +// let params = +// parse(revocation_url_params.trim_start_matches('?').as_bytes()); + +// for (name, value) in params { +// revocation_request = revocation_request +// .add_extra_param(name.into_owned(), value.into_owned()); +// } +// } + +// revocation_request +// .request_async(async_http_client) +// .await +// .map_err(|err| ShieldError::Request(err.to_string()))?; +// } +// } +// } diff --git a/packages/methods/shield-oidc/src/method.rs b/packages/methods/shield-oidc/src/method.rs index 61bbd7b..6c115d9 100644 --- a/packages/methods/shield-oidc/src/method.rs +++ b/packages/methods/shield-oidc/src/method.rs @@ -1,10 +1,10 @@ use std::sync::Arc; use async_trait::async_trait; -use shield::{Action, Method, ShieldError, User, erased_method}; +use shield::{Method, MethodAction, ShieldError, User, erased_method}; use crate::{ - actions::{OidcSignInAction, OidcSignInCallbackAction, OidcSignOutAction}, + actions::{OidcSignInAction, OidcSignInCallbackAction}, options::OidcOptions, provider::OidcProvider, session::OidcSession, @@ -71,14 +71,13 @@ impl Method for OidcMethod { OIDC_METHOD_ID.to_owned() } - fn actions(&self) -> Vec>> { + fn actions(&self) -> Vec>> { vec![ Box::new(OidcSignInAction::new(self.options.clone())), Box::new(OidcSignInCallbackAction::new( self.options.clone(), self.storage.clone(), )), - Box::new(OidcSignOutAction), ] } diff --git a/packages/methods/shield-workos/src/actions.rs b/packages/methods/shield-workos/src/actions.rs index 37fd419..5b37ca6 100644 --- a/packages/methods/shield-workos/src/actions.rs +++ b/packages/methods/shield-workos/src/actions.rs @@ -1,11 +1,9 @@ mod index; mod sign_in; -mod sign_out; mod sign_up; pub use index::*; pub use sign_in::*; -pub use sign_out::*; pub use sign_up::*; // TODO: diff --git a/packages/methods/shield-workos/src/actions/index.rs b/packages/methods/shield-workos/src/actions/index.rs index 9e40728..3469457 100644 --- a/packages/methods/shield-workos/src/actions/index.rs +++ b/packages/methods/shield-workos/src/actions/index.rs @@ -3,9 +3,9 @@ use std::sync::Arc; use async_trait::async_trait; use serde::Deserialize; use shield::{ - Action, ActionMethod, Form, Input, InputType, InputTypeEmail, InputTypeHidden, InputTypeSubmit, - InputValue, MethodSession, Request, Response, ResponseType, ShieldError, SignInAction, - SignUpAction, erased_action, + Form, Input, InputType, InputTypeEmail, InputTypeHidden, InputTypeSubmit, InputValue, + MethodAction, MethodSession, Request, RequestMethod, Response, ResponseType, ShieldError, + SignInAction, SignUpAction, erased_method_action, }; use workos::{ PaginationParams, @@ -41,7 +41,7 @@ impl WorkosIndexAction { } #[async_trait] -impl Action for WorkosIndexAction { +impl MethodAction for WorkosIndexAction { fn id(&self) -> String { ACTION_ID.to_owned() } @@ -58,8 +58,8 @@ impl Action for WorkosIndexAction { "Index with WorkOS." } - fn method(&self) -> ActionMethod { - ActionMethod::Post + fn method(&self) -> RequestMethod { + RequestMethod::Post } async fn forms(&self, _provider: WorkosProvider) -> Result, ShieldError> { @@ -260,4 +260,4 @@ impl Action for WorkosIndexAction { } } -erased_action!(WorkosIndexAction); +erased_method_action!(WorkosIndexAction); diff --git a/packages/methods/shield-workos/src/actions/sign_in.rs b/packages/methods/shield-workos/src/actions/sign_in.rs index d937c62..ca08818 100644 --- a/packages/methods/shield-workos/src/actions/sign_in.rs +++ b/packages/methods/shield-workos/src/actions/sign_in.rs @@ -2,9 +2,9 @@ use std::sync::Arc; use async_trait::async_trait; use shield::{ - Action, ActionMethod, Form, Input, InputType, InputTypeEmail, InputTypeHidden, - InputTypePassword, InputTypeSubmit, InputValue, MethodSession, Request, Response, ResponseType, - ShieldError, SignInAction, erased_action, + Form, Input, InputType, InputTypeEmail, InputTypeHidden, InputTypePassword, InputTypeSubmit, + InputValue, MethodAction, MethodSession, Request, RequestMethod, Response, ResponseType, + ShieldError, SignInAction, erased_method_action, }; use crate::{client::WorkosClient, provider::WorkosProvider}; @@ -22,7 +22,7 @@ impl WorkosSignInAction { } #[async_trait] -impl Action for WorkosSignInAction { +impl MethodAction for WorkosSignInAction { fn id(&self) -> String { SignInAction::id() } @@ -39,8 +39,8 @@ impl Action for WorkosSignInAction { "Sign in with WorkOS." } - fn method(&self) -> ActionMethod { - ActionMethod::Post + fn method(&self) -> RequestMethod { + RequestMethod::Post } async fn forms(&self, _provider: WorkosProvider) -> Result, ShieldError> { @@ -127,4 +127,4 @@ impl Action for WorkosSignInAction { } } -erased_action!(WorkosSignInAction); +erased_method_action!(WorkosSignInAction); diff --git a/packages/methods/shield-workos/src/actions/sign_out.rs b/packages/methods/shield-workos/src/actions/sign_out.rs deleted file mode 100644 index 383fc3f..0000000 --- a/packages/methods/shield-workos/src/actions/sign_out.rs +++ /dev/null @@ -1,69 +0,0 @@ -use std::sync::Arc; - -use async_trait::async_trait; -use shield::{ - Action, ActionMethod, Form, MethodSession, Request, Response, ResponseType, SessionAction, - ShieldError, SignOutAction, erased_action, -}; - -use crate::{client::WorkosClient, provider::WorkosProvider}; - -pub struct WorkosSignOutAction { - // TODO: Remove expect. - #[expect(unused)] - client: Arc, -} - -impl WorkosSignOutAction { - pub fn new(client: Arc) -> Self { - Self { client } - } -} - -#[async_trait] -impl Action for WorkosSignOutAction { - fn id(&self) -> String { - SignOutAction::id() - } - - fn name(&self) -> String { - SignOutAction::name() - } - - fn openapi_summary(&self) -> &'static str { - "Sign out with WorkOS" - } - - fn openapi_description(&self) -> &'static str { - "Sign out with WorkOS." - } - - fn method(&self) -> ActionMethod { - ActionMethod::Post - } - - fn condition( - &self, - provider: &WorkosProvider, - session: &MethodSession<()>, - ) -> Result { - SignOutAction::condition(provider, session) - } - - async fn forms(&self, provider: WorkosProvider) -> Result, ShieldError> { - SignOutAction::forms(provider).await - } - - async fn call( - &self, - _provider: WorkosProvider, - _session: &MethodSession<()>, - _request: Request, - ) -> Result { - // TODO: Handle WorkOS sign out. - - Ok(Response::new(ResponseType::Default).session_action(SessionAction::Unauthenticate)) - } -} - -erased_action!(WorkosSignOutAction); diff --git a/packages/methods/shield-workos/src/actions/sign_up.rs b/packages/methods/shield-workos/src/actions/sign_up.rs index ce29f82..4611248 100644 --- a/packages/methods/shield-workos/src/actions/sign_up.rs +++ b/packages/methods/shield-workos/src/actions/sign_up.rs @@ -2,9 +2,9 @@ use std::sync::Arc; use async_trait::async_trait; use shield::{ - Action, ActionMethod, Form, Input, InputType, InputTypeEmail, InputTypeHidden, - InputTypePassword, InputTypeSubmit, InputValue, MethodSession, Request, Response, ResponseType, - ShieldError, SignUpAction, erased_action, + Form, Input, InputType, InputTypeEmail, InputTypeHidden, InputTypePassword, InputTypeSubmit, + InputValue, MethodAction, MethodSession, Request, RequestMethod, Response, ResponseType, + ShieldError, SignUpAction, erased_method_action, }; use crate::{client::WorkosClient, provider::WorkosProvider}; @@ -22,7 +22,7 @@ impl WorkosSignUpAction { } #[async_trait] -impl Action for WorkosSignUpAction { +impl MethodAction for WorkosSignUpAction { fn id(&self) -> String { SignUpAction::id() } @@ -39,8 +39,8 @@ impl Action for WorkosSignUpAction { "Sign up with WorkOS." } - fn method(&self) -> ActionMethod { - ActionMethod::Post + fn method(&self) -> RequestMethod { + RequestMethod::Post } async fn forms(&self, _provider: WorkosProvider) -> Result, ShieldError> { @@ -127,4 +127,4 @@ impl Action for WorkosSignUpAction { } } -erased_action!(WorkosSignUpAction); +erased_method_action!(WorkosSignUpAction); diff --git a/packages/methods/shield-workos/src/method.rs b/packages/methods/shield-workos/src/method.rs index a08c97a..c3bcddf 100644 --- a/packages/methods/shield-workos/src/method.rs +++ b/packages/methods/shield-workos/src/method.rs @@ -1,16 +1,18 @@ use std::sync::Arc; use async_trait::async_trait; -use shield::{Action, Method, ShieldError, erased_method}; +use shield::{Method, MethodAction, ShieldError, erased_method}; use workos::{ApiKey, WorkOs}; use crate::{ - actions::{WorkosIndexAction, WorkosSignInAction, WorkosSignOutAction, WorkosSignUpAction}, + actions::{WorkosIndexAction, WorkosSignInAction, WorkosSignUpAction}, client::WorkosClient, options::WorkosOptions, provider::WorkosProvider, }; +// TODO: Add hook for WorkOS sign out. + pub const WORKOS_METHOD_ID: &str = "workos"; pub struct WorkosMethod { @@ -45,7 +47,7 @@ impl Method for WorkosMethod { WORKOS_METHOD_ID.to_owned() } - fn actions(&self) -> Vec>> { + fn actions(&self) -> Vec>> { vec![ Box::new(WorkosIndexAction::new( self.options.clone(), @@ -53,7 +55,6 @@ impl Method for WorkosMethod { )), Box::new(WorkosSignInAction::new(self.client.clone())), Box::new(WorkosSignUpAction::new(self.client.clone())), - Box::new(WorkosSignOutAction::new(self.client.clone())), ] } diff --git a/packages/styles/shield-bootstrap/src/dioxus.rs b/packages/styles/shield-bootstrap/src/dioxus.rs index 587e173..3e0ff04 100644 --- a/packages/styles/shield-bootstrap/src/dioxus.rs +++ b/packages/styles/shield-bootstrap/src/dioxus.rs @@ -1,11 +1,12 @@ mod form; mod input; +mod method_form; use dioxus::prelude::*; use shield::ActionForms; use shield_dioxus::{DioxusStyle, ErasedDioxusStyle}; -use crate::dioxus::form::Form; +use crate::dioxus::{form::Form, method_form::MethodForm}; #[derive(Default)] pub struct BootstrapDioxusStyle {} @@ -26,9 +27,16 @@ impl DioxusStyle for BootstrapDioxusStyle { "{action.name}" } + for form in &action.forms { + Form { + action_id: action.id.clone(), + form: form.clone() + } + } + for method_form in &action.method_forms { for provider_form in &method_form.provider_forms { - Form { + MethodForm { action_id: action.id.clone(), method_id: method_form.id.clone(), provider_id: provider_form.id.clone(), diff --git a/packages/styles/shield-bootstrap/src/dioxus/form.rs b/packages/styles/shield-bootstrap/src/dioxus/form.rs index 18cdc0a..2a1fff2 100644 --- a/packages/styles/shield-bootstrap/src/dioxus/form.rs +++ b/packages/styles/shield-bootstrap/src/dioxus/form.rs @@ -9,8 +9,6 @@ use crate::dioxus::input::FormInput; #[derive(Clone, PartialEq, Props)] pub struct FormProps { action_id: String, - method_id: String, - provider_id: Option, form: shield::Form, } @@ -23,8 +21,6 @@ pub fn Form(props: FormProps) -> Element { onsubmit: { move |event| { let action_id = props.action_id.clone(); - let method_id = props.method_id.clone(); - let provider_id = props.provider_id.clone(); event.prevent_default(); @@ -43,7 +39,7 @@ pub fn Form(props: FormProps) -> Element { .collect::>() ).expect("TODO: handle error"); - let result = call(action_id, method_id, provider_id, data).await; + let result = call(action_id, data).await; match result { Ok(response) => { diff --git a/packages/styles/shield-bootstrap/src/dioxus/method_form.rs b/packages/styles/shield-bootstrap/src/dioxus/method_form.rs new file mode 100644 index 0000000..29c8a26 --- /dev/null +++ b/packages/styles/shield-bootstrap/src/dioxus/method_form.rs @@ -0,0 +1,78 @@ +use std::collections::HashMap; + +use dioxus::{logger::tracing::info, prelude::*}; +use shield::ResponseType; +use shield_dioxus::{ShieldRouter, call_method}; + +use crate::dioxus::input::FormInput; + +#[derive(Clone, PartialEq, Props)] +pub struct MethodFormProps { + action_id: String, + method_id: String, + provider_id: Option, + form: shield::Form, +} + +#[component] +pub fn MethodForm(props: MethodFormProps) -> Element { + let navigator = navigator(); + + rsx! { + form { + onsubmit: { + move |event| { + let action_id = props.action_id.clone(); + let method_id = props.method_id.clone(); + let provider_id = props.provider_id.clone(); + + event.prevent_default(); + + async move { + info!("{:?}", event); + let data = serde_json::to_value( + // TODO: Support inputs with `multiple` attribute. + event + .data() + .values() + .into_iter() + .filter_map(|(key, value)| match value { + FormValue::Text(value) => Some((key, value)), + FormValue::File(_) => None, + }) + .collect::>() + ).expect("TODO: handle error"); + + let result = call_method(action_id, method_id, provider_id, data).await; + + match result { + Ok(response) => { + info!("{:?}", response); + + match response { + ResponseType::Default => {}, + ResponseType::Redirect(to) => { + navigator.push(to); + }, + ResponseType::RedirectToAction { action_id } => { + navigator.push(ShieldRouter::Action { action_id, query: "".to_owned() }); + } + } + } + Err(err) => { + // TODO: Handle error. + error!("{err}"); + } + } + } + } + }, + + for input in props.form.inputs { + FormInput { + input: input, + } + } + } + } +} diff --git a/packages/styles/shield-bootstrap/src/leptos.rs b/packages/styles/shield-bootstrap/src/leptos.rs index d935c6e..b8534cc 100644 --- a/packages/styles/shield-bootstrap/src/leptos.rs +++ b/packages/styles/shield-bootstrap/src/leptos.rs @@ -1,11 +1,12 @@ mod form; mod input; +mod method_form; use leptos::prelude::*; use shield::ActionForms; use shield_leptos::{ErasedLeptosStyle, LeptosStyle}; -use crate::leptos::form::Form; +use crate::leptos::{form::Form, method_form::MethodForm}; #[derive(Default)] pub struct BootstrapLeptosStyle {} @@ -22,8 +23,15 @@ impl LeptosStyle for BootstrapLeptosStyle {

{action.name.clone()}

- {action.method_forms.iter().flat_map(|method_form| method_form.provider_forms.iter().map(|provider_form| view! { + {action.forms.iter().map(|form| view! { + }).collect_view()} + + {action.method_forms.iter().flat_map(|method_form| method_form.provider_forms.iter().map(|provider_form| view! { + , - form: shield::Form, -) -> impl IntoView { +pub fn Form(action_id: String, form: shield::Form) -> impl IntoView { let call = ServerAction::::new(); view! { - - {form.inputs.into_iter().map(|input| view! { diff --git a/packages/styles/shield-bootstrap/src/leptos/method_form.rs b/packages/styles/shield-bootstrap/src/leptos/method_form.rs new file mode 100644 index 0000000..4e4b4e1 --- /dev/null +++ b/packages/styles/shield-bootstrap/src/leptos/method_form.rs @@ -0,0 +1,26 @@ +use leptos::prelude::*; +use shield_leptos::CallMethod; + +use crate::leptos::input::FormInput; + +#[component] +pub fn MethodForm( + action_id: String, + method_id: String, + provider_id: Option, + form: shield::Form, +) -> impl IntoView { + let call_method = ServerAction::::new(); + + view! { + + + + + + {form.inputs.into_iter().map(|input| view! { + + }).collect_view()} + + } +}