commit 958d9a19df0d99a749d840fab2371de75ec3925b Author: geoffsee <> Date: Sun May 11 17:04:52 2025 -0400 init diff --git a/.dev.vars b/.dev.vars new file mode 100644 index 0000000..b05df4c --- /dev/null +++ b/.dev.vars @@ -0,0 +1,13 @@ +CLIENT_ID="your-value-here" + +CLIENT_SECRET="your-value-here" + +AUTH_SERVER_URL="your-value-here" + +APP_URL="http://localhost:3000" + +DEV_MODE="true" + +ZITADEL_ORG_ID="your-value-here" + +ZITADEL_PROJECT_ID="your-value-here" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f21515b --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/target/ +/node_modules/ +/.wrangler/ +/.idea/ +/build/ \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..e52c892 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,3868 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" + +[[package]] +name = "async-trait" +version = "0.1.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f934833b4b7233644e5848f235df3f57ed8c80f1528a26c3dfa13d2147fa056" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "axum" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" +dependencies = [ + "async-trait", + "axum-core 0.4.5", + "axum-macros", + "bytes", + "futures-util", + "http 1.3.1", + "http-body 1.0.1", + "http-body-util", + "hyper 1.6.0", + "hyper-util", + "itoa", + "matchit 0.7.3", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper 1.0.2", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d6fd624c75e18b3b4c6b9caf42b1afe24437daaee904069137d8bab077be8b8" +dependencies = [ + "axum-core 0.5.0", + "bytes", + "futures-util", + "http 1.3.1", + "http-body 1.0.1", + "http-body-util", + "itoa", + "matchit 0.8.4", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "sync_wrapper 1.0.2", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http 1.3.1", + "http-body 1.0.1", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper 1.0.2", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1362f362fd16024ae199c1970ce98f9661bf5ef94b9808fee734bc3698b733" +dependencies = [ + "bytes", + "futures-util", + "http 1.3.1", + "http-body 1.0.1", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper 1.0.2", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-extra" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fc6f625a1f7705c6cf62d0d070794e94668988b1c38111baeec177c715f7b" +dependencies = [ + "axum 0.8.1", + "axum-core 0.5.0", + "bytes", + "cookie", + "futures-util", + "headers", + "http 1.3.1", + "http-body 1.0.1", + "http-body-util", + "mime", + "pin-project-lite", + "serde", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d123550fa8d071b7255cb0cc04dc302baa6c8c4a79f55701552684d8399bce" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "biscuit" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e28fc7c56c61743a01d0d1b73e4fed68b8a4f032ea3a2d4bb8c6520a33fc05a" +dependencies = [ + "chrono", + "data-encoding", + "num-bigint", + "num-traits", + "once_cell", + "ring", + "serde", + "serde_json", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" + +[[package]] +name = "bytecount" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ce89b21cab1437276d2650d57e971f9d548a2d9037cc231abdc0562b97498ce" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" + +[[package]] +name = "camino" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", +] + +[[package]] +name = "cc" +version = "1.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4730490333d58093109dc02c23174c3f4d490998c3fed3cc8e82d57afedb9cf" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "chrono-tz" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59ae0466b83e838b81a54256c39d5d7c20b9d7daa10510a242d9b75abd5936e" +dependencies = [ + "chrono", + "chrono-tz-build", + "phf", +] + +[[package]] +name = "chrono-tz-build" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "433e39f13c9a060046954e0592a8d0a4bcb1040125cbf91cb8ee58964cfb350f" +dependencies = [ + "parse-zoneinfo", + "phf", + "phf_codegen", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "cookie" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "aes-gcm", + "base64 0.22.1", + "hkdf", + "hmac", + "percent-encoding", + "rand", + "sha2", + "subtle", + "time", + "version_check", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "rand_core", + "typenum", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "custom_error" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f8a51dd197fa6ba5b4dc98a990a43cc13693c23eb0089ebb0fcc1f04152bca6" + +[[package]] +name = "darling" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "data-encoding" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e60eed09d8c01d3cee5b7d30acb059b76614c918fa0f992e0dd6eeb10daad6f" + +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", + "serde", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dyn-clone" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "feeef44e73baff3a26d371801df019877a9866a8c493d315ab00177843314f35" + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" +dependencies = [ + "curve25519-dalek", + "ed25519", + "serde", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "hkdf", + "pem-rfc7468", + "pkcs8", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "error-chain" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" +dependencies = [ + "version_check", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", +] + +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "glob" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + +[[package]] +name = "h2" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap 2.7.1", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" + +[[package]] +name = "headers" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "322106e6bd0cba2d5ead589ddb8150a13d7c4217cf80d7c4f682ca994ccc6aa9" +dependencies = [ + "base64 0.21.7", + "bytes", + "headers-core", + "http 1.3.1", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" +dependencies = [ + "http 1.3.1", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http 1.3.1", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes", + "futures-util", + "http 1.3.1", + "http-body 1.0.1", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http 0.2.12", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.3.1", + "http-body 1.0.1", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http 0.2.12", + "hyper 0.14.32", + "rustls", + "tokio", + "tokio-rustls", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper 1.6.0", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.3.1", + "http-body 1.0.1", + "hyper 1.6.0", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" +dependencies = [ + "equivalent", + "hashbrown 0.15.2", + "serde", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "iri-string" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "jsonwebtoken" +version = "9.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ae10193d25051e74945f1ea2d0b42e03cc3b890f7e4cc5faa44997d808193f" +dependencies = [ + "base64 0.21.7", + "js-sys", + "pem", + "ring", + "serde", + "serde_json", + "simple_asn1", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] + +[[package]] +name = "libc" +version = "0.2.169" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" + +[[package]] +name = "libm" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "litemap" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", + "serde", +] + +[[package]] +name = "log" +version = "0.4.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" + +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mini-moka" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c325dfab65f261f386debee8b0969da215b3fa0037e74c8a1234db7ba986d803" +dependencies = [ + "crossbeam-channel", + "crossbeam-utils", + "dashmap", + "skeptic", + "smallvec", + "tagptr", + "triomphe", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.52.0", +] + +[[package]] +name = "native-tls" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dab59f8e050d5df8e4dd87d9206fb6f65a483e20ac9fda365ade4fab353196c" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "oauth2" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c38841cdd844847e3e7c8d29cef9dcfed8877f8f56f9071f77843ecf3baf937f" +dependencies = [ + "base64 0.13.1", + "chrono", + "getrandom 0.2.15", + "http 0.2.12", + "rand", + "reqwest 0.11.27", + "serde", + "serde_json", + "serde_path_to_error", + "sha2", + "thiserror 1.0.69", + "url", +] + +[[package]] +name = "oauth2" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51e219e79014df21a225b1860a479e2dcd7cbd9130f4defd4bd0e191ea31d67d" +dependencies = [ + "base64 0.22.1", + "chrono", + "getrandom 0.2.15", + "http 1.3.1", + "rand", + "reqwest 0.12.12", + "serde", + "serde_json", + "serde_path_to_error", + "sha2", + "thiserror 1.0.69", + "url", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "openid" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b5451f4eddd020a516f1a333f43112a62955935be5f78c02fde0bb701f36a1e" +dependencies = [ + "base64 0.22.1", + "biscuit", + "chrono", + "lazy_static", + "mime", + "reqwest 0.12.12", + "serde", + "serde_json", + "thiserror 1.0.69", + "url", + "validator", +] + +[[package]] +name = "openidconnect" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f47e80a9cfae4462dd29c41e987edd228971d6565553fbc14b8a11e666d91590" +dependencies = [ + "base64 0.13.1", + "chrono", + "dyn-clone", + "ed25519-dalek", + "hmac", + "http 0.2.12", + "itertools", + "log", + "oauth2 4.4.2", + "p256", + "p384", + "rand", + "rsa", + "serde", + "serde-value", + "serde_derive", + "serde_json", + "serde_path_to_error", + "serde_plain", + "serde_with", + "sha2", + "subtle", + "thiserror 1.0.69", + "url", +] + +[[package]] +name = "openssl" +version = "0.10.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5e534d133a060a3c19daec1eb3e98ec6f4685978834f2dbadfe2ec215bab64e" +dependencies = [ + "bitflags 2.8.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-sys" +version = "0.9.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "ordered-float" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" +dependencies = [ + "num-traits", +] + +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "p384" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70786f51bcc69f6a4c0360e063a4cac5419ef7c5cd5b3c99ad70f3be5ba79209" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "parse-zoneinfo" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f2a05b18d44e2957b88f96ba460715e295bc1d7510468a2f3d3b44535d26c24" +dependencies = [ + "regex", +] + +[[package]] +name = "pem" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" +dependencies = [ + "base64 0.22.1", + "serde", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e2ec53ad785f4d35dac0adea7f7dc6f1bb277ad84a680c7afefeae05d1f5916" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56a66c0c55993aa927429d0f8a0abfd74f084e4d9c192cffed01e418d83eefb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" + +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "pulldown-cmark" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57206b407293d2bcd3af849ce869d52068623f19e1b5ff8e8778e3309439682b" +dependencies = [ + "bitflags 2.8.0", + "memchr", + "unicase", +] + +[[package]] +name = "quote" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.15", +] + +[[package]] +name = "redox_syscall" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +dependencies = [ + "bitflags 2.8.0", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "reqwest" +version = "0.11.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +dependencies = [ + "base64 0.21.7", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.32", + "hyper-rustls", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls", + "rustls-pemfile 1.0.4", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 0.1.2", + "system-configuration", + "tokio", + "tokio-rustls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots", + "winreg", +] + +[[package]] +name = "reqwest" +version = "0.12.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-core", + "futures-util", + "http 1.3.1", + "http-body 1.0.1", + "http-body-util", + "hyper 1.6.0", + "hyper-tls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile 2.2.0", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 1.0.2", + "tokio", + "tokio-native-tls", + "tower", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-registry", +] + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.15", + "libc", + "spin", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rsa" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47c75d7c5c6b673e58bf54d8544a9f432e3a925b0e80f7cd3602ab5c50c55519" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core", + "signature", + "spki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.8.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustls" +version = "0.21.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "log", + "ring", + "rustls-webpki", + "sct", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" + +[[package]] +name = "ryu" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.8.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03" +dependencies = [ + "serde", +] + +[[package]] +name = "serde" +version = "1.0.217" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-value" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" +dependencies = [ + "ordered-float", + "serde", +] + +[[package]] +name = "serde-wasm-bindgen" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3b143e2833c57ab9ad3ea280d21fd34e285a42837aeb0ee301f4f41890fa00e" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "serde-wasm-bindgen" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "serde_derive" +version = "1.0.217" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.138" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949" +dependencies = [ + "indexmap 2.7.1", + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" +dependencies = [ + "itoa", + "serde", +] + +[[package]] +name = "serde_plain" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1fc6db65a611022b23a0dec6975d63fb80a302cb3388835ff02c097258d50" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa" +dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.7.1", + "serde", + "serde_derive", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core", +] + +[[package]] +name = "simple_asn1" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror 2.0.11", + "time", +] + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "skeptic" +version = "0.13.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16d23b015676c90a0f01c197bfdc786c20342c73a0afdda9025adb0bc42940a8" +dependencies = [ + "bytecount", + "cargo_metadata", + "error-chain", + "glob", + "pulldown-cmark", + "tempfile", + "walkdir", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "socket2" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tagptr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" + +[[package]] +name = "tempfile" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91" +dependencies = [ + "cfg-if", + "fastrand", + "getrandom 0.3.2", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" +dependencies = [ + "thiserror-impl 2.0.11", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "time" +version = "0.3.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" +dependencies = [ + "deranged", + "itoa", + "js-sys", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.43.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper 1.0.2", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-cookies" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fd0118512cf0b3768f7fcccf0bef1ae41d68f2b45edc1e77432b36c97c56c6d" +dependencies = [ + "async-trait", + "axum-core 0.4.5", + "cookie", + "futures-util", + "http 1.3.1", + "parking_lot", + "pin-project-lite", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.2" +source = "git+https://github.com/tower-rs/tower-http#45ce10bb10eba45a37b4343471c1a075145832cd" +dependencies = [ + "bitflags 2.8.0", + "bytes", + "futures-util", + "http 1.3.1", + "http-body 1.0.1", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "tracing", + "uuid", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tower-sessions" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65856c81ee244e0f8a55ab0f7b769b72fbde387c235f0a73cd97c579818d05eb" +dependencies = [ + "async-trait", + "http 1.3.1", + "time", + "tokio", + "tower-cookies", + "tower-layer", + "tower-service", + "tower-sessions-core", + "tower-sessions-memory-store", + "tracing", +] + +[[package]] +name = "tower-sessions-core" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb6abbfcaf6436ec5a772cd9f965401da12db793e404ae6134eac066fa5a04f3" +dependencies = [ + "async-trait", + "axum-core 0.4.5", + "base64 0.22.1", + "futures", + "http 1.3.1", + "parking_lot", + "rand", + "serde", + "serde_json", + "thiserror 1.0.69", + "time", + "tokio", + "tracing", +] + +[[package]] +name = "tower-sessions-memory-store" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fad75660c8afbe74f4e7cbbe8e9090171a056b57370ea4d7d5e9eb3e4af3092" +dependencies = [ + "async-trait", + "time", + "tokio", + "tower-sessions-core", +] + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-serde" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" +dependencies = [ + "serde", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "serde", + "serde_json", + "sharded-slab", + "thread_local", + "time", + "tracing-core", + "tracing-serde", +] + +[[package]] +name = "tracing-web" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e6a141feebd51f8d91ebfd785af50fca223c570b86852166caa3b141defe7c" +dependencies = [ + "js-sys", + "tracing-core", + "tracing-subscriber", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "triomphe" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef8f7726da4807b58ea5c96fdc122f80702030edc33b35aff9190a51148ccc85" + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicase" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" + +[[package]] +name = "unicode-ident" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3758f5e68192bb96cc8f9b7e2c2cfdabb435499a28499a42f8f984092adad4b" +dependencies = [ + "getrandom 0.2.15", +] + +[[package]] +name = "validator" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0b4a29d8709210980a09379f27ee31549b73292c87ab9899beee1c0d3be6303" +dependencies = [ + "idna", + "once_cell", + "regex", + "serde", + "serde_derive", + "serde_json", + "url", + "validator_derive", +] + +[[package]] +name = "validator_derive" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bac855a2ce6f843beb229757e6e570a42e837bcb15e5f449dd48d5747d41bf77" +dependencies = [ + "darling", + "once_cell", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "0.25.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-link" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" + +[[package]] +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags 2.8.0", +] + +[[package]] +name = "worker" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "727789ca7eff9733efbea9d0e97779edc1cf1926e98aee7d7d8afe32805458aa" +dependencies = [ + "async-trait", + "axum 0.7.9", + "bytes", + "chrono", + "chrono-tz", + "futures-channel", + "futures-util", + "http 1.3.1", + "http-body 1.0.1", + "js-sys", + "matchit 0.7.3", + "pin-project", + "serde", + "serde-wasm-bindgen 0.6.5", + "serde_json", + "serde_urlencoded", + "tokio", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "worker-kv 0.7.0", + "worker-macros", + "worker-sys", +] + +[[package]] +name = "worker-kv" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f06d4d1416a9f8346ee9123b0d9a11b3cfa38e6cfb5a139698017d1597c4d41" +dependencies = [ + "js-sys", + "serde", + "serde-wasm-bindgen 0.5.0", + "serde_json", + "thiserror 1.0.69", + "wasm-bindgen", + "wasm-bindgen-futures", +] + +[[package]] +name = "worker-kv" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7988dab9d6ff43088405bacce07cfcd723e9688497766e8a645bc91e888b636f" +dependencies = [ + "js-sys", + "serde", + "serde-wasm-bindgen 0.5.0", + "serde_json", + "thiserror 1.0.69", + "wasm-bindgen", + "wasm-bindgen-futures", +] + +[[package]] +name = "worker-macros" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d625c24570ba9207a2617476013335f28a95cbe513e59bb814ffba092a18058" +dependencies = [ + "async-trait", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-macro-support", + "worker-sys", +] + +[[package]] +name = "worker-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34563340d41016b4381257c5a16b0d2bc590dbe00500ecfbebcaa16f5f85ce90" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zitadel-session-worker" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "axum 0.7.9", + "axum-core 0.4.5", + "axum-extra", + "base64 0.22.1", + "bytes", + "chrono", + "console_error_panic_hook", + "custom_error", + "http 1.3.1", + "http-body-util", + "js-sys", + "jsonwebtoken", + "mini-moka", + "oauth2 5.0.0", + "openid", + "openidconnect", + "reqwest 0.11.27", + "ring", + "serde", + "serde-wasm-bindgen 0.5.0", + "serde_json", + "serde_urlencoded", + "thiserror 1.0.69", + "time", + "tokio", + "tower", + "tower-cookies", + "tower-http", + "tower-service", + "tower-sessions", + "tower-sessions-core", + "tracing", + "tracing-subscriber", + "tracing-web", + "url", + "uuid", + "wasm-bindgen-futures", + "worker", + "worker-kv 0.8.0", + "worker-macros", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..610b887 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,76 @@ +[package] +name = "zitadel-session-worker" +version = "0.1.0" +edition = "2021" +authors = [ "Geoff Seemueller <28698553+geoffsee@users.noreply.github.com>" ] + +[package.metadata.release] +release = false + +# https://github.com/rustwasm/wasm-pack/issues/1247 +[package.metadata.wasm-pack.profile.release] +wasm-opt = false + +[lib] +crate-type = ["cdylib"] + +[dependencies] +worker = { version="0.5.0", features=['http', 'axum', 'd1', 'timezone'] } +worker-macros = { version="0.5.0", features=['http'] } +axum = { version = "0.7.9", default-features = false, features = ["macros", "json", "query", "original-uri", "tracing", "http1", "matched-path"] } +tower-service = "0.3.2" +console_error_panic_hook = { version = "0.1.1" } +reqwest = { version = "0.11.27", features = ["json", "rustls-tls"], default-features = false } +thiserror = "1.0.69" +openidconnect = { version = "3.5.0", features = ["reqwest"]} +serde_urlencoded = {version = "0.7.1"} +url = "2.5.4" +oauth2 = { version = "=5.0.0", optional = false, default-features = false, features = ["reqwest"] } +custom_error = {version = "1.9.2"} +serde_json = { version = "1.0.116" } +# this is set to version 1.0.200 in zitadel rust library +serde = { version = "1.0.217", features = ["derive"] } +jsonwebtoken = { version = "9.3.0"} +axum-extra = { version = "0.10.0", features = ["typed-header", "cookie"] } +base64 = "0.22.1" +js-sys = "0.3" +time = { version = "0.3" , default-features = false, features = ["wasm-bindgen", "serde"], optional = false} +async-trait = { version = "0.1.80"} +tokio = { version = "1.43.0", default-features = false, features = ["macros","rt"] } +wasm-bindgen-futures = "0.4.50" +serde-wasm-bindgen = "0.5" +openid = "0.16.1" +anyhow = "1.0.95" +tower-sessions = { version = "=0.13", default-features = false, features = ["memory-store", "signed", "private", "axum-core"] } +tower = { version = "0.5.2", features = ["tokio", "tracing"] } +tower-http = { git = "https://github.com/tower-rs/tower-http", default-features = false, features = ["cors", "set-header", "sensitive-headers", "trace", "propagate-header", "follow-redirect", "request-id"] } +chrono = { version = "0.4.40", features = ["wasmbind"] } +tower-sessions-core = { version = "0.13"} +worker-kv = "0.8.0" +tracing = { version = "0.1" } +tracing-subscriber = { version = "0.3", default-features = false, features = ['json', 'time'] } +tracing-web = "0.1" +axum-core = { version = "0.4.5", features = ["tracing"] } +http = "1.3.1" +bytes = "1.9.0" +mini-moka = "0.10.3" +tower-cookies = "0.10.0" +uuid = {version = "1.12.1", features = ["v4"]} + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +ring = { version = "0.17.4", features = ["std"] } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +js-sys = "0.3" +ring = { version = "0.17.4", features = ["std", "wasm32_unknown_unknown_js"] } + + +[dev-dependencies] +chrono = "0.4.38" +tower = { version = "0.5.2" } +tokio = { version = "1.37.0", features = ["macros", "rt-multi-thread"] } +http-body-util = {version = "0.1.2"} + + +[profile.release] +allow-unsafe = true diff --git a/README.md b/README.md new file mode 100644 index 0000000..c988522 --- /dev/null +++ b/README.md @@ -0,0 +1,222 @@ +# zitadel-session-worker + +> ⚠️ **WARNING**: This project is currently in development and **NOT** production-ready. Use at your own risk. It may +> contain bugs, security vulnerabilities, or incomplete features. This should +> serve as a starting point for anyone building similar technology. All feedback is welcome. + +A Rust Cloudflare Worker that provides authentication and session management for web applications using ZITADEL as the identity provider. It adopts the implementation for oauth2 token introspection from [smartive/zitadel-rs](https://github.com/smartive/zitadel-rust). + +## Overview + +This project is a Rust-based Cloudflare Worker that acts as an authentication proxy for web applications. It handles: + +- oauth2/oidc w/PKCE via Zitadel +- Session management using Cloudflare KV storage +- Token introspection and validation +- Proxying authenticated requests to backend services + +When deployed, the worker sits between your users and your application services. It: +1. Intercepts incoming requests +2. Verifies if the user has a valid session +3. If not, redirects to ZITADEL for authentication +4. After successful authentication, creates a session and proxies the request to your service +5. For subsequent requests, validates the session and proxies authenticated requests + + +> **Note**: Caches are used by the introspection and session modules. They prevent excessive r/w. + +## Prerequisites + +- [Rust](https://www.rust-lang.org/tools/install) (latest stable version) +- LLVM and clang +- [Bun](https://bun.sh/) JavaScript runtime +- [Wrangler CLI](https://developers.cloudflare.com/workers/wrangler/) for Cloudflare Workers development +- ZITADEL Administrator Access + +## Installation + +1. Clone the repository: + ```bash + git clone + cd zitadel-session-worker + ``` + +2. Install dependencies: + ```bash + # Install JavaScript dependencies + bun install + ``` + +## Configuration + +> **Note**: There is a docker compose file with Zitadel in this repository that can be used for testing. + +### Environment Variables + +Create a `.dev.vars` file in the project root with the following variables: + +``` +CLIENT_ID="your-client-id" +CLIENT_SECRET="your-client-secret" +AUTH_SERVER_URL="your-zitadel-instance-url" +ZITADEL_ORG_ID="your-organization-id" +ZITADEL_PROJECT_ID="your-project-id" +APP_URL="http://localhost:3000" +DEV_MODE="true" +``` + +### Wrangler Configuration + +- `wrangler.jsonc` - Base configuration + +## Development + +### Running Locally + +```bash +# Start the development server +bun run dev +``` + +This will start the worker on `localhost:3000`. + +### Building + +```bash +# Build the project +cargo clean && cargo install -q worker-build && worker-build --release +``` + +## Deployment + +### Deploying to Cloudflare + +```bash +# Deploy to development environment +bun run deploy:dev + +# Deploy with updated secrets +bun run deploy:dev:secrets +``` + +### Viewing Logs + +```bash +# View logs from the deployed worker +bun run tail:dev +``` + +## Integration with Your Application + +To integrate this worker with your existing application: + +1. **Configure Cloudflare**: + - Set up a Cloudflare Worker route that points to your application domain + - Deploy this worker to that route + +2. **Configure ZITADEL**: + - Create an application in ZITADEL + - Configure the redirect URI to `https://your-worker-domain/login/callback` + - Get the client ID and client secret + +3. **Configure this Worker**: + - Update the environment variables with your ZITADEL credentials + - Set the `APP_URL` to your application's URL + - Set an http route in `wrangler.jsonc` + +4. **Access Control**: + - The worker will automatically handle authentication + - Your application will receive authenticated requests with user information + - You can access user information via the `/api/whoami` endpoint + +## Testing + +The project uses Rust's built-in testing framework with tokio for async tests. + +```bash +# Run all tests +cargo test +``` + +### Adding New Tests + +1. For unit tests, add them to the `tests` module in the relevant source file +2. For async tests, use the `#[tokio::test]` attribute +3. Follow the existing pattern of testing both success and error cases +4. Mock external dependencies when necessary + +## Debugging + +1. For local development, use `console_log!` macros to output debug information +2. View logs in the wrangler development console +3. For deployed workers, use `bun run tail:dev` to stream logs +4. Check the `/api/whoami` endpoint to verify user authentication and session data + +## Project Structure + +- `src/` - Rust source code + - `api/` - API endpoints and routing + - `axum_introspector/` - Axum framework integration for token introspection + - `credentials/` - Credential management + - `oidc/` - OpenID Connect implementation + - `session_storage/` - Session storage implementations + - `utilities.rs` - Utility functions + - `lib.rs` - Main entry point and worker setup + +## Contributing + +Contributions to this project are welcome! Here are some guidelines: + +1. **Fork the repository** and create your branch from `main` +2. **Install dependencies** and ensure you can build the project +3. **Make your changes** and add or update tests as necessary +4. **Ensure tests pass** by running `cargo test` +5. **Format your code** with `cargo fmt` +6. **Submit a pull request** with a clear description of your changes + +### Code Style + +- Follow Rust's standard code style and idioms +- Use `cargo fmt` to format code +- Use `cargo clippy` for linting + +## Acknowledgements + +This project is made possible thanks to: + +- **ZITADEL**: For providing the robust identity management platform that powers this authentication proxy +- **Smartive**: For [zitadel-rs](https://github.com/smartive/zitadel-rust) +- **Cloudflare**: For their Workers platform and KV storage solution +- **Open Source Community**: For the various dependencies and tools that make this project possible: + - The Rust ecosystem and its crates + - The Axum web framework + - The Tower middleware ecosystem + - Various other open-source projects listed in our dependencies + +I appreciate the hard work and dedication of all the developers and organizations that contribute to the open-source +ecosystem. + + +## License + +MIT License + +Copyright (c) 2025 Geoff Seemueller + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..d75f2ae --- /dev/null +++ b/bun.lock @@ -0,0 +1,207 @@ +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "dependencies": { + "wrangler": "latest", + }, + }, + }, + "packages": { + "@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.4.0", "", { "dependencies": { "mime": "^3.0.0" } }, "sha512-+tv3z+SPp+gqTIcImN9o0hqE9xyfQjI1XD9pL6NuKjua9B1y7mNYv0S9cP+QEbA4ppVgGZEmKOvHX5G5Ei1CVA=="], + + "@cloudflare/unenv-preset": ["@cloudflare/unenv-preset@2.3.1", "", { "peerDependencies": { "unenv": "2.0.0-rc.15", "workerd": "^1.20250320.0" }, "optionalPeers": ["workerd"] }, "sha512-Xq57Qd+ADpt6hibcVBO0uLG9zzRgyRhfCUgBT9s+g3+3Ivg5zDyVgLFy40ES1VdNcu8rPNSivm9A+kGP5IVaPg=="], + + "@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20250428.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-6nVe9oV4Hdec6ctzMtW80TiDvNTd2oFPi3VsKqSDVaJSJbL+4b6seyJ7G/UEPI+si6JhHBSLV2/9lNXNGLjClA=="], + + "@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20250428.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-/TB7bh7SIJ5f+6r4PHsAz7+9Qal/TK1cJuKFkUno1kqGlZbdrMwH0ATYwlWC/nBFeu2FB3NUolsTntEuy23hnQ=="], + + "@cloudflare/workerd-linux-64": ["@cloudflare/workerd-linux-64@1.20250428.0", "", { "os": "linux", "cpu": "x64" }, "sha512-9eCbj+R3CKqpiXP6DfAA20DxKge+OTj7Hyw3ZewiEhWH9INIHiJwJQYybu4iq9kJEGjnGvxgguLFjSCWm26hgg=="], + + "@cloudflare/workerd-linux-arm64": ["@cloudflare/workerd-linux-arm64@1.20250428.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-D9NRBnW46nl1EQsP13qfkYb5lbt4C6nxl38SBKY/NOcZAUoHzNB5K0GaK8LxvpkM7X/97ySojlMfR5jh5DNXYQ=="], + + "@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20250428.0", "", { "os": "win32", "cpu": "x64" }, "sha512-RQCRj28eitjKD0tmei6iFOuWqMuHMHdNGEigRmbkmuTlpbWHNAoHikgCzZQ/dkKDdatA76TmcpbyECNf31oaTA=="], + + "@cspotcode/source-map-support": ["@cspotcode/source-map-support@0.8.1", "", { "dependencies": { "@jridgewell/trace-mapping": "0.3.9" } }, "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw=="], + + "@emnapi/runtime": ["@emnapi/runtime@1.4.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ=="], + + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.25.2", "", { "os": "android", "cpu": "arm" }, "sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.2", "", { "os": "android", "cpu": "arm64" }, "sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.25.2", "", { "os": "android", "cpu": "x64" }, "sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.2", "", { "os": "linux", "cpu": "arm" }, "sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.2", "", { "os": "linux", "cpu": "ia32" }, "sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.2", "", { "os": "linux", "cpu": "none" }, "sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.2", "", { "os": "linux", "cpu": "none" }, "sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.2", "", { "os": "linux", "cpu": "none" }, "sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.2", "", { "os": "linux", "cpu": "x64" }, "sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.2", "", { "os": "none", "cpu": "arm64" }, "sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.2", "", { "os": "none", "cpu": "x64" }, "sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.2", "", { "os": "win32", "cpu": "x64" }, "sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA=="], + + "@fastify/busboy": ["@fastify/busboy@2.1.1", "", {}, "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA=="], + + "@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.0.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ=="], + + "@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.0.4" }, "os": "darwin", "cpu": "x64" }, "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q=="], + + "@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.0.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg=="], + + "@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.0.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ=="], + + "@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.0.5", "", { "os": "linux", "cpu": "arm" }, "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g=="], + + "@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.0.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA=="], + + "@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.0.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA=="], + + "@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.0.4", "", { "os": "linux", "cpu": "x64" }, "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw=="], + + "@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.0.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA=="], + + "@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.0.4", "", { "os": "linux", "cpu": "x64" }, "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw=="], + + "@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.0.5" }, "os": "linux", "cpu": "arm" }, "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ=="], + + "@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.0.4" }, "os": "linux", "cpu": "arm64" }, "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA=="], + + "@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.0.4" }, "os": "linux", "cpu": "s390x" }, "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q=="], + + "@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.0.4" }, "os": "linux", "cpu": "x64" }, "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA=="], + + "@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" }, "os": "linux", "cpu": "arm64" }, "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g=="], + + "@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.0.4" }, "os": "linux", "cpu": "x64" }, "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw=="], + + "@img/sharp-wasm32": ["@img/sharp-wasm32@0.33.5", "", { "dependencies": { "@emnapi/runtime": "^1.2.0" }, "cpu": "none" }, "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg=="], + + "@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.33.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ=="], + + "@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.33.5", "", { "os": "win32", "cpu": "x64" }, "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.0", "", {}, "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.9", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ=="], + + "acorn": ["acorn@8.14.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA=="], + + "acorn-walk": ["acorn-walk@8.3.2", "", {}, "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A=="], + + "as-table": ["as-table@1.0.55", "", { "dependencies": { "printable-characters": "^1.0.42" } }, "sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ=="], + + "blake3-wasm": ["blake3-wasm@2.1.5", "", {}, "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g=="], + + "color": ["color@4.2.3", "", { "dependencies": { "color-convert": "^2.0.1", "color-string": "^1.9.0" } }, "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A=="], + + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + + "color-string": ["color-string@1.9.1", "", { "dependencies": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" } }, "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg=="], + + "cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], + + "data-uri-to-buffer": ["data-uri-to-buffer@2.0.2", "", {}, "sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA=="], + + "defu": ["defu@6.1.4", "", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="], + + "detect-libc": ["detect-libc@2.0.4", "", {}, "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA=="], + + "esbuild": ["esbuild@0.25.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.2", "@esbuild/android-arm": "0.25.2", "@esbuild/android-arm64": "0.25.2", "@esbuild/android-x64": "0.25.2", "@esbuild/darwin-arm64": "0.25.2", "@esbuild/darwin-x64": "0.25.2", "@esbuild/freebsd-arm64": "0.25.2", "@esbuild/freebsd-x64": "0.25.2", "@esbuild/linux-arm": "0.25.2", "@esbuild/linux-arm64": "0.25.2", "@esbuild/linux-ia32": "0.25.2", "@esbuild/linux-loong64": "0.25.2", "@esbuild/linux-mips64el": "0.25.2", "@esbuild/linux-ppc64": "0.25.2", "@esbuild/linux-riscv64": "0.25.2", "@esbuild/linux-s390x": "0.25.2", "@esbuild/linux-x64": "0.25.2", "@esbuild/netbsd-arm64": "0.25.2", "@esbuild/netbsd-x64": "0.25.2", "@esbuild/openbsd-arm64": "0.25.2", "@esbuild/openbsd-x64": "0.25.2", "@esbuild/sunos-x64": "0.25.2", "@esbuild/win32-arm64": "0.25.2", "@esbuild/win32-ia32": "0.25.2", "@esbuild/win32-x64": "0.25.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ=="], + + "exit-hook": ["exit-hook@2.2.1", "", {}, "sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw=="], + + "exsolve": ["exsolve@1.0.5", "", {}, "sha512-pz5dvkYYKQ1AHVrgOzBKWeP4u4FRb3a6DNK2ucr0OoNwYIU4QWsJ+NM36LLzORT+z845MzKHHhpXiUF5nvQoJg=="], + + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "get-source": ["get-source@2.0.12", "", { "dependencies": { "data-uri-to-buffer": "^2.0.0", "source-map": "^0.6.1" } }, "sha512-X5+4+iD+HoSeEED+uwrQ07BOQr0kEDFMVqqpBuI+RaZBpBpHCuXxo70bjar6f0b0u/DQJsJ7ssurpP0V60Az+w=="], + + "glob-to-regexp": ["glob-to-regexp@0.4.1", "", {}, "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw=="], + + "is-arrayish": ["is-arrayish@0.3.2", "", {}, "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="], + + "mime": ["mime@3.0.0", "", { "bin": { "mime": "cli.js" } }, "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A=="], + + "miniflare": ["miniflare@4.20250428.1", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "acorn": "8.14.0", "acorn-walk": "8.3.2", "exit-hook": "2.2.1", "glob-to-regexp": "0.4.1", "stoppable": "1.1.0", "undici": "^5.28.5", "workerd": "1.20250428.0", "ws": "8.18.0", "youch": "3.3.4", "zod": "3.22.3" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-M3qcJXjeAEimHrEeWXEhrJiC3YHB5M3QSqqK67pOTI+lHn0QyVG/2iFUjVJ/nv+i10uxeAEva8GRGeu+tKRCmQ=="], + + "mustache": ["mustache@4.2.0", "", { "bin": { "mustache": "bin/mustache" } }, "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ=="], + + "ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="], + + "path-to-regexp": ["path-to-regexp@6.3.0", "", {}, "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ=="], + + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + + "printable-characters": ["printable-characters@1.0.42", "", {}, "sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ=="], + + "semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="], + + "sharp": ["sharp@0.33.5", "", { "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.3", "semver": "^7.6.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.33.5", "@img/sharp-darwin-x64": "0.33.5", "@img/sharp-libvips-darwin-arm64": "1.0.4", "@img/sharp-libvips-darwin-x64": "1.0.4", "@img/sharp-libvips-linux-arm": "1.0.5", "@img/sharp-libvips-linux-arm64": "1.0.4", "@img/sharp-libvips-linux-s390x": "1.0.4", "@img/sharp-libvips-linux-x64": "1.0.4", "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", "@img/sharp-libvips-linuxmusl-x64": "1.0.4", "@img/sharp-linux-arm": "0.33.5", "@img/sharp-linux-arm64": "0.33.5", "@img/sharp-linux-s390x": "0.33.5", "@img/sharp-linux-x64": "0.33.5", "@img/sharp-linuxmusl-arm64": "0.33.5", "@img/sharp-linuxmusl-x64": "0.33.5", "@img/sharp-wasm32": "0.33.5", "@img/sharp-win32-ia32": "0.33.5", "@img/sharp-win32-x64": "0.33.5" } }, "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw=="], + + "simple-swizzle": ["simple-swizzle@0.2.2", "", { "dependencies": { "is-arrayish": "^0.3.1" } }, "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg=="], + + "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + + "stacktracey": ["stacktracey@2.1.8", "", { "dependencies": { "as-table": "^1.0.36", "get-source": "^2.0.12" } }, "sha512-Kpij9riA+UNg7TnphqjH7/CzctQ/owJGNbFkfEeve4Z4uxT5+JapVLFXcsurIfN34gnTWZNJ/f7NMG0E8JDzTw=="], + + "stoppable": ["stoppable@1.1.0", "", {}, "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw=="], + + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "ufo": ["ufo@1.6.1", "", {}, "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA=="], + + "undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="], + + "unenv": ["unenv@2.0.0-rc.15", "", { "dependencies": { "defu": "^6.1.4", "exsolve": "^1.0.4", "ohash": "^2.0.11", "pathe": "^2.0.3", "ufo": "^1.5.4" } }, "sha512-J/rEIZU8w6FOfLNz/hNKsnY+fFHWnu9MH4yRbSZF3xbbGHovcetXPs7sD+9p8L6CeNC//I9bhRYAOsBt2u7/OA=="], + + "workerd": ["workerd@1.20250428.0", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20250428.0", "@cloudflare/workerd-darwin-arm64": "1.20250428.0", "@cloudflare/workerd-linux-64": "1.20250428.0", "@cloudflare/workerd-linux-arm64": "1.20250428.0", "@cloudflare/workerd-windows-64": "1.20250428.0" }, "bin": { "workerd": "bin/workerd" } }, "sha512-JJNWkHkwPQKQdvtM9UORijgYdcdJsihA4SfYjwh02IUQsdMyZ9jizV1sX9yWi9B9ptlohTW8UNHJEATuphGgdg=="], + + "wrangler": ["wrangler@4.14.1", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.4.0", "@cloudflare/unenv-preset": "2.3.1", "blake3-wasm": "2.1.5", "esbuild": "0.25.2", "miniflare": "4.20250428.1", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.15", "workerd": "1.20250428.0" }, "optionalDependencies": { "fsevents": "~2.3.2", "sharp": "^0.33.5" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20250428.0" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-EU7IThP7i68TBftJJSveogvWZ5k/WRijcJh3UclDWiWWhDZTPbL6LOJEFhHKqFzHOaC4Y2Aewt48rfTz0e7oCw=="], + + "ws": ["ws@8.18.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw=="], + + "youch": ["youch@3.3.4", "", { "dependencies": { "cookie": "^0.7.1", "mustache": "^4.2.0", "stacktracey": "^2.1.8" } }, "sha512-UeVBXie8cA35DS6+nBkls68xaBBXCye0CNznrhszZjTbRVnJKQuNsyLKBTTL4ln1o1rh2PKtv35twV7irj5SEg=="], + + "zod": ["zod@3.22.3", "", {}, "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug=="], + } +} diff --git a/compose.yml b/compose.yml new file mode 100644 index 0000000..1d0ba44 --- /dev/null +++ b/compose.yml @@ -0,0 +1,48 @@ +version: '3.8' + +services: + zitadel: + restart: 'always' + networks: + - 'zitadel' + image: 'ghcr.io/zitadel/zitadel:latest' + command: 'start-from-init --masterkey "MasterkeyNeedsToHave32Characters" --tlsMode disabled' + environment: + # - 'ZITADEL_INITIAL_USER=zitadel-admin@zitadel.localhost' + # - 'ZITADEL_INITIAL_PASSWORD=Password1!' + - 'ZITADEL_DATABASE_POSTGRES_HOST=db' + - 'ZITADEL_DATABASE_POSTGRES_PORT=5432' + - 'ZITADEL_DATABASE_POSTGRES_DATABASE=zitadel' + - 'ZITADEL_DATABASE_POSTGRES_USER_USERNAME=zitadel' + - 'ZITADEL_DATABASE_POSTGRES_USER_PASSWORD=zitadel' + - 'ZITADEL_DATABASE_POSTGRES_USER_SSL_MODE=disable' + - 'ZITADEL_DATABASE_POSTGRES_ADMIN_USERNAME=postgres' + - 'ZITADEL_DATABASE_POSTGRES_ADMIN_PASSWORD=postgres' + - 'ZITADEL_DATABASE_POSTGRES_ADMIN_SSL_MODE=disable' + - 'ZITADEL_EXTERNALSECURE=false' + depends_on: + db: + condition: 'service_healthy' + ports: + - '8080:8080' + + db: + restart: 'always' + image: postgres:16-alpine + environment: + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=postgres + - POSTGRES_DB=zitadel + networks: + - 'zitadel' + healthcheck: + test: ["CMD-SHELL", "pg_isready", "-d", "zitadel", "-U", "postgres"] + interval: '10s' + timeout: '30s' + retries: 5 + start_period: '20s' + ports: + - '5432:5432' + +networks: + zitadel: \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..a7b3a8b --- /dev/null +++ b/package.json @@ -0,0 +1,12 @@ +{ + "scripts": { + "dev": "bun operate:env:local dev", + "dev:example-service": "(cd ../_sample_workers/example-service && bunx wrangler dev)", + "deploy:dev": "wrangler deploy", + "deploy:dev:secrets": "wrangler versions secret bulk secrets.json && bun run deploy:dev", + "tail:dev": "wrangler tail" + }, + "dependencies": { + "wrangler": "latest" + } +} diff --git a/secrets.json b/secrets.json new file mode 100644 index 0000000..c08167b --- /dev/null +++ b/secrets.json @@ -0,0 +1,9 @@ +{ + "CLIENT_ID": "", + "CLIENT_SECRET": "", + "AUTH_SERVER_URL": "", + "ZITADEL_ORG_ID": "", + "ZITADEL_PROJECT_ID": "", + "APP_URL": "https://your-worker.workers.dev", + "DEV_MODE": "false" +} \ No newline at end of file diff --git a/src/api/authenticated.rs b/src/api/authenticated.rs new file mode 100644 index 0000000..d1017c0 --- /dev/null +++ b/src/api/authenticated.rs @@ -0,0 +1,20 @@ +use crate::axum_introspector::introspection::IntrospectedUser; +use crate::AppState; +use axum::extract::{Request, State}; +use axum::response::IntoResponse; +use tower::Layer; +use tower_service::Service; +use worker::*; + +pub struct AuthenticatedApi; + +impl AuthenticatedApi { + #[worker::send] + pub async fn proxy(session: tower_sessions::Session, State(state): State, user: IntrospectedUser, mut request: Request) -> impl IntoResponse { + let worker_request = worker::Request::try_from(request).unwrap(); + let http_request = http::Request::try_from(worker_request).unwrap(); + + let proxy_target = state.env.service("PROXY_TARGET").unwrap(); + as Into>::into(proxy_target.fetch_request(http_request).await.expect("failed to proxy request")) + } +} \ No newline at end of file diff --git a/src/api/mod.rs b/src/api/mod.rs new file mode 100644 index 0000000..16ed456 --- /dev/null +++ b/src/api/mod.rs @@ -0,0 +1,2 @@ +pub mod public; +pub mod authenticated; \ No newline at end of file diff --git a/src/api/public.rs b/src/api/public.rs new file mode 100644 index 0000000..a9c6f80 --- /dev/null +++ b/src/api/public.rs @@ -0,0 +1,353 @@ +use crate::utilities::Utilities; +use crate::{AppState, Callback}; +use axum::extract::{Query, Request, State}; +use axum::response::IntoResponse; +use oauth2::basic::BasicClient; +use oauth2::{ + AuthUrl, AuthorizationCode, ClientId, ClientSecret, CsrfToken, PkceCodeChallenge, + PkceCodeVerifier, RedirectUrl, Scope, TokenResponse, TokenUrl, +}; +use std::str::FromStr; +use std::sync::Arc; +use tower::Layer; +use tower_service::Service; +use tower_sessions_core::Session; +use worker::*; + +pub struct PublicApi; + +impl PublicApi { + #[worker::send] + pub async fn fallback() -> impl IntoResponse { + return axum::response::Response::builder() + .status(http::StatusCode::NOT_FOUND) + .body(Body::empty()) + .unwrap(); + } + + #[worker::send] + pub async fn login_page(session: Session, request: Request) -> impl IntoResponse { + session + .insert("last_visited", chrono::Local::now().to_string()) + .await + .unwrap(); + + session.save().await.unwrap(); + + axum::response::Html( + r#" + + + + + + Redirecting... + + + +

Redirecting to login...

+
+ +
+ + +"#, + ) + .into_response() + } + + #[worker::send] + pub async fn authorize( + session: tower_sessions::Session, + State(state): State, + ) -> impl IntoResponse { + let oauth_base_url = state.env.secret("AUTH_SERVER_URL").unwrap().to_string(); + let app_host = state.env.secret("APP_URL").unwrap().to_string(); + + let redirect_uri = format!("{}{}", app_host, "/login/callback"); + + let client = BasicClient::new(ClientId::new( + state.env.secret("CLIENT_ID").unwrap().to_string(), + )) + .set_client_secret(ClientSecret::new( + state.env.secret("CLIENT_SECRET").unwrap().to_string(), + )) + .set_auth_uri(AuthUrl::new(format!("{}{}", oauth_base_url, "/oauth/v2/authorize")).unwrap()) + .set_token_uri(TokenUrl::new(format!("{}{}", oauth_base_url, "/oauth/v2/token")).unwrap()) + .set_redirect_uri(RedirectUrl::new(redirect_uri).unwrap()); + + // Generate a PKCE challenge. + let (pkce_challenge, pkce_verifier) = PkceCodeChallenge::new_random_sha256(); + + let org_scope: String = if let Ok(org_id) = state.env.secret("ZITADEL_ORG_ID") { + format!("urn:zitadel:iam:org:id:{}", org_id.to_string()) + } else { + String::new() + }; + let project_scope: String = if let Ok(project_id) = state.env.secret("ZITADEL_PROJECT_ID") { + format!( + "urn:zitadel:iam:org:project:id:{}:aud", + project_id.to_string() + ) + } else { + String::new() + }; + + let mut scopes = vec![ + Scope::new("openid".to_string()), + Scope::new("email".to_string()), + // Scope::new("profile".to_string()), + // Scope::new("offline_access".to_string()) + ]; + + if (!org_scope.is_empty()) { + scopes.push(Scope::new(org_scope)); + } + if (!project_scope.is_empty()) { + scopes.push(Scope::new(project_scope)); + } + + let (auth_url, csrf_token) = client + .authorize_url(CsrfToken::new_random) + .add_scopes(scopes) + .set_pkce_challenge(pkce_challenge) + .url(); + + let csrf_string = csrf_token.secret().to_string(); + let verifier_storage_key = Utilities::get_pkce_verifier_storage_key(&csrf_string); // Use a key tied to the state param + + if let Some(csrf_state) = session.get::("csrf_state").await.unwrap() { + if csrf_state != csrf_string { + console_error!("CSRF state mismatch."); + return axum::response::Response::builder() + .status(http::StatusCode::BAD_REQUEST) + .body(axum::body::Body::empty()) + .unwrap(); + } + } else { + session + .insert( + verifier_storage_key.as_str(), + pkce_verifier.secret().as_str(), + ) + .await + .unwrap(); + session + .insert("csrf_state", csrf_string.as_str()) + .await + .unwrap(); + session.save().await.unwrap(); + } + let csrf_store = state.env.kv("KV_STORAGE").unwrap(); + + let session_csrf_key = Utilities::get_auth_session_key(csrf_string.as_str()); + + csrf_store + .put(session_csrf_key.as_str(), session.id().unwrap().to_string()) + .unwrap() + .execute() + .await + .unwrap(); + + let final_auth_url = auth_url.as_str(); + + let redirect_response = http::Response::builder() + .status(http::StatusCode::FOUND) + .header(http::header::LOCATION, final_auth_url) + .body(axum::body::Body::empty()) + .unwrap(); + + redirect_response.into_response() + } + + #[worker::send] + pub async fn callback( + State(state): State, + mut session: tower_sessions::Session, + callback: Query, + request: Request, + ) -> impl IntoResponse { + let code = &callback.code; + let state_param = &callback.state; + + if code.is_empty() { + return axum::response::Response::builder() + .status(http::StatusCode::BAD_REQUEST) + .body(axum::body::Body::from("Invalid authorization code")) + .unwrap(); + } + + let verifier_storage_key = Utilities::get_pkce_verifier_storage_key(state_param); + + let csrf_store = state.env.kv("KV_STORAGE").unwrap(); + + let csrf_key = Utilities::get_auth_session_key(state_param); + + let get_auth_session_id = csrf_store + .get(csrf_key.as_str()) + .text() + .await + .expect("failed to get auth session id"); + + csrf_store.delete(csrf_key.as_str()).await.unwrap(); + + let asi = get_auth_session_id.map(|data| data).unwrap(); + + let auth_session_id = tower_sessions_core::session::Id::from_str(asi.as_str()).unwrap(); + + let mut auth_session = + Session::new(Some(auth_session_id), Arc::new(state.session_store), None); + + let verifier_string: String = match auth_session.get(verifier_storage_key.as_str()).await { + Ok(Some(v)) => v, + Ok(None) => { + console_error!( + "PKCE verifier not found in session for key: {:?}", + verifier_storage_key + ); + return axum::response::Response::builder() + .status(http::StatusCode::BAD_REQUEST) + .body(axum::body::Body::from("Session state mismatch or expired.")) + .unwrap(); + } + Err(e) => { + console_error!("Error retrieving PKCE verifier from session: {:?}", e); + return axum::response::Response::builder() + .status(http::StatusCode::INTERNAL_SERVER_ERROR) + .body(axum::body::Body::from( + "Internal server error retrieving session data.", + )) + .unwrap(); + } + }; + + let stored_csrf_state: String = match auth_session.get("csrf_state").await { + Ok(Some(s)) => s, + Ok(None) => { + console_error!("CSRF state not found in session."); + return axum::response::Response::builder() + .status(http::StatusCode::BAD_REQUEST) + .body(axum::body::Body::from("CSRF state mismatch or missing.")) + .unwrap(); + } + Err(e) => { + console_error!("Error retrieving CSRF state from session: {:?}", e); + return axum::response::Response::builder() + .status(http::StatusCode::INTERNAL_SERVER_ERROR) + .body(axum::body::Body::from( + "Internal server error retrieving session data.", + )) + .unwrap(); + } + }; + + // Basic CSRF state verification + if &stored_csrf_state != state_param { + console_error!( + "CSRF state mismatch. Expected: {:?}, Received: {:?}", + stored_csrf_state, + state_param + ); + return axum::response::Response::builder() + .status(http::StatusCode::BAD_REQUEST) + .body(axum::body::Body::empty()) + .unwrap(); + } else { + auth_session.remove::("csrf_state").await.unwrap(); + } + + let pkce_verifier = PkceCodeVerifier::new(verifier_string); + // console_log!("callback::pkce_verifier: {:?}", pkce_verifier.secret().to_string()); + auth_session + .remove::(verifier_storage_key.as_str()) + .await + .unwrap(); + + let oauth_base_url = state.env.secret("AUTH_SERVER_URL").unwrap().to_string(); + let app_host = state.env.secret("HOST").unwrap().to_string(); + let redirect_uri = format!("{}{}", app_host, "/login/callback"); + + let redirect_url = RedirectUrl::new(redirect_uri).unwrap(); + + let client = BasicClient::new(ClientId::new( + state.env.secret("CLIENT_ID").unwrap().to_string(), + )) + .set_client_secret(ClientSecret::new( + state + .env + .secret("CLIENT_SECRET") + .unwrap() + .to_string(), + )) + .set_auth_uri(AuthUrl::new(format!("{}{}", oauth_base_url, "/oauth/v2/authorize")).unwrap()) + .set_token_uri(TokenUrl::new(format!("{}{}", oauth_base_url, "/oauth/v2/token")).unwrap()) + .set_redirect_uri(redirect_url); + + let http_client = oauth2::reqwest::ClientBuilder::new() + .build() + .expect("Client should build"); + + match client + .exchange_code(AuthorizationCode::new(code.to_string())) + .set_pkce_verifier(pkce_verifier) + .request_async(&http_client) + .await + { + Ok(token_result) => { + session + .insert("token", token_result.access_token().secret().to_string()) + .await + .unwrap(); + + session.save().await.unwrap(); + + let url = request.uri(); + let mut redirect_location = Url::parse(url.to_string().as_str()).unwrap(); + redirect_location.set_path("/"); + redirect_location.set_query(None); + + console_log!("redirecting to : {:?}", redirect_location); + + let session_response = Session::from(session).save().await.unwrap().into_response(); + let session_headers = session_response.headers(); + + let mut redirect_response = axum::response::Response::builder() + .status(http::StatusCode::FOUND) + .header(http::header::LOCATION, redirect_location.as_str()) + .body(axum::body::Body::empty()) + .unwrap() + .into_response(); + + for (key, value) in session_headers.iter() { + redirect_response.headers_mut().insert(key, value.clone()); + } + redirect_response.into_response() + } + Err(e) => { + console_log!("Token request failed: {:?}", e); + let error_message = match e { + oauth2::RequestTokenError::ServerResponse(server_error) => { + format!("Server error: {:?}", server_error) + } + _ => format!("Unknown error: {:?}", e), + }; + return axum::response::Response::builder() + .status(http::StatusCode::INTERNAL_SERVER_ERROR) + .body(axum::body::Body::from(format!( + "OAuth2 Token Error: {}", + error_message + ))) + .unwrap(); + } + } + } +} diff --git a/src/axum_introspector/introspection/mod.rs b/src/axum_introspector/introspection/mod.rs new file mode 100644 index 0000000..03c35bf --- /dev/null +++ b/src/axum_introspector/introspection/mod.rs @@ -0,0 +1,7 @@ +mod state; +mod state_builder; +mod user; + +pub use state::IntrospectionState; +pub use state_builder::{IntrospectionStateBuilder, IntrospectionStateBuilderError}; +pub use user::{IntrospectedUser, IntrospectionGuardError}; diff --git a/src/axum_introspector/introspection/state.rs b/src/axum_introspector/introspection/state.rs new file mode 100644 index 0000000..f6addad --- /dev/null +++ b/src/axum_introspector/introspection/state.rs @@ -0,0 +1,18 @@ +use openidconnect::IntrospectionUrl; +use std::sync::Arc; + +use crate::oidc::introspection::cache::IntrospectionCache; +use crate::oidc::introspection::AuthorityAuthentication; + +#[derive(Clone, Debug)] +pub struct IntrospectionState { + pub(crate) config: Arc, +} + +#[derive(Debug)] +pub(crate) struct IntrospectionConfig { + pub(crate) authority: String, + pub(crate) authentication: AuthorityAuthentication, + pub(crate) introspection_uri: IntrospectionUrl, + pub(crate) cache: Option>, +} diff --git a/src/axum_introspector/introspection/state_builder.rs b/src/axum_introspector/introspection/state_builder.rs new file mode 100644 index 0000000..b0e39c7 --- /dev/null +++ b/src/axum_introspector/introspection/state_builder.rs @@ -0,0 +1,91 @@ +use custom_error::custom_error; +use std::sync::Arc; + +use crate::axum_introspector::introspection::state::IntrospectionConfig; +use crate::credentials::Application; +use crate::oidc::discovery::{discover, DiscoveryError}; +use crate::oidc::introspection::AuthorityAuthentication; + +use crate::oidc::introspection::cache::IntrospectionCache; + +use super::state::IntrospectionState; + +custom_error! { + pub IntrospectionStateBuilderError + NoAuthSchema = "no authentication for authority defined", + Discovery{source: DiscoveryError} = "could not fetch discovery document: {source}", + NoIntrospectionUrl = "discovery document did not contain an introspection url", +} + +pub struct IntrospectionStateBuilder { + authority: String, + authentication: Option, + cache: Option>, +} + +impl IntrospectionStateBuilder { + pub fn new(authority: &str) -> Self { + Self { + authority: authority.to_string(), + authentication: None, + cache: None, + } + } + + pub fn with_basic_auth( + &mut self, + client_id: &str, + client_secret: &str, + ) -> &mut IntrospectionStateBuilder { + self.authentication = Some(AuthorityAuthentication::Basic { + client_id: client_id.to_string(), + client_secret: client_secret.to_string(), + }); + + self + } + + pub fn with_jwt_profile(&mut self, application: Application) -> &mut IntrospectionStateBuilder { + self.authentication = Some(AuthorityAuthentication::JWTProfile { application }); + + self + } + + pub fn with_introspection_cache( + &mut self, + cache: impl IntrospectionCache + 'static, + ) -> &mut IntrospectionStateBuilder { + self.cache = Some(Box::new(cache)); + + self + } + + pub async fn build(&mut self) -> Result { + if self.authentication.is_none() { + return Err(IntrospectionStateBuilderError::NoAuthSchema); + } + + let metadata = discover(&self.authority) + .await + .map_err(|source| IntrospectionStateBuilderError::Discovery { source })?; + + let introspection_uri = metadata + .additional_metadata() + .introspection_endpoint + .clone(); + + if introspection_uri.is_none() { + return Err(IntrospectionStateBuilderError::NoIntrospectionUrl); + } + + Ok(IntrospectionState { + config: Arc::new(IntrospectionConfig { + authority: self.authority.clone(), + introspection_uri: introspection_uri.unwrap(), + authentication: self.authentication.as_ref().unwrap().clone(), + // #[cfg(feature = "introspection_cache")] + cache: self.cache.take(), + }), + }) + } +} diff --git a/src/axum_introspector/introspection/user.rs b/src/axum_introspector/introspection/user.rs new file mode 100644 index 0000000..9a3a62b --- /dev/null +++ b/src/axum_introspector/introspection/user.rs @@ -0,0 +1,497 @@ +use async_trait::async_trait; +use axum::extract::{FromRef, FromRequestParts}; +use axum::http::request::Parts; +use axum::response::IntoResponse; +use axum::{Json, RequestPartsExt}; +use custom_error::custom_error; +use openidconnect::TokenIntrospectionResponse; +use serde::Serialize; +use std::collections::HashMap; +use std::future::Future; +use std::pin::Pin; +use std::sync::Arc; +use worker::console_log; + +use crate::axum_introspector::introspection::IntrospectionState; +use crate::oidc::introspection::{introspect, IntrospectionError, ZitadelIntrospectionResponse}; + +custom_error! { + pub IntrospectionGuardError + MissingConfig = "no introspection cdktf given to rocket managed state", + Unauthorized = "no HTTP authorization header found", + InvalidHeader = "authorization header is invalid", + WrongScheme = "Authorization header is not a bearer token", + Introspection{source: IntrospectionError} = "introspection returned an error: {source}", + Inactive = "access token is inactive", + NoUserId = "introspection result contained no user id", +} + +impl IntoResponse for IntrospectionGuardError { + fn into_response(self) -> axum::response::Response { + use axum::http::StatusCode; + use serde_json::json; + let (status, error_message) = match self { + IntrospectionGuardError::MissingConfig => { + (StatusCode::INTERNAL_SERVER_ERROR, "missing config") + } + IntrospectionGuardError::Unauthorized => (StatusCode::UNAUTHORIZED, "unauthorized"), + IntrospectionGuardError::InvalidHeader => (StatusCode::BAD_REQUEST, "invalid header"), + IntrospectionGuardError::WrongScheme => (StatusCode::BAD_REQUEST, "invalid schema"), + IntrospectionGuardError::Introspection { source: _ } => { + (StatusCode::BAD_REQUEST, "introspection error") + } + IntrospectionGuardError::Inactive => (StatusCode::FORBIDDEN, "user is inactive"), + IntrospectionGuardError::NoUserId => (StatusCode::NOT_FOUND, "user was not found"), + }; + + let body = Json(json!({ + "error": error_message, + })); + + ( + status, + [("x-introspection-error", error_message)], + body, + ) + .into_response() + } +} + +#[derive(Serialize,Debug)] +pub struct IntrospectedUser { + pub user_id: String, + pub username: Option, + pub name: Option, + pub given_name: Option, + pub family_name: Option, + pub preferred_username: Option, + pub email: Option, + pub email_verified: Option, + pub locale: Option, + pub project_roles: Option>>, + pub metadata: Option>, +} + +// +// On wasm32, define a newtype that wraps a future and unsafely marks it as Send. +// This is safe on single-threaded targets. +// +#[cfg(target_arch = "wasm32")] +struct NonSendFuture(F); + +#[cfg(target_arch = "wasm32")] +use std::task::Poll; + +#[cfg(target_arch = "wasm32")] +use std::task::Context; + +#[cfg(target_arch = "wasm32")] +impl Future for NonSendFuture { + type Output = F::Output; + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + // SAFETY: Delegate polling to the inner future. + unsafe { self.map_unchecked_mut(|s| &mut s.0) }.poll(cx) + } +} + +#[cfg(target_arch = "wasm32")] +unsafe impl Send for NonSendFuture {} + +#[cfg(target_arch = "wasm32")] +fn wrap_future(f: F) -> Pin + Send>> +where + F: Future + 'static, +{ + Box::pin(NonSendFuture(f)) +} + +#[cfg(not(target_arch = "wasm32"))] +fn wrap_future(f: F) -> Pin + Send>> +where + F: Future + Send + 'static, +{ + Box::pin(f) +} +// +#[async_trait] +impl FromRequestParts for IntrospectedUser +where + S: 'static + Sync, + IntrospectionState: FromRef, + tower_sessions_core::Session: FromRequestParts, +{ + type Rejection = IntrospectionGuardError; + + async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { + let mut parts_clone = parts.clone(); + + let session = tower_sessions_core::Session::from_request_parts(&mut parts_clone, state) + .await + .ok(); + + let unwrapped_session = session.unwrap(); + + let _ = unwrapped_session.load().await.unwrap(); + + let token = if let Some(tok) = unwrapped_session.get::("token").await.ok() { + if !tok.is_none() { + Some(tok.unwrap()) + } else { + None + } + } else { + let token_from_header = Self::token_from_header(parts)?; + + Some(token_from_header) + }; + + let introspection_state = IntrospectionState::from_ref(state); + let config = Arc::clone(&introspection_state.config); + + let fut = async move { + let result = match introspection_state.config.cache.as_deref() { + None => { + introspect( + &config.introspection_uri, + &config.authority, + &config.authentication, + &token.unwrap(), + ) + .await + } + Some(cache) => match cache.get(token.clone().unwrap_or(String::new()).as_str()).await { + Some(cached_response) => Ok(cached_response), + None => { + let res = introspect( + &config.introspection_uri, + &config.authority, + &config.authentication, + token.clone().unwrap_or(String::new()).as_str(), + ) + .await; + if let Ok(res) = &res { + cache + .set(token.clone().unwrap().as_str(), res.clone()) + .await; + } + res + } + }, + }; + + let user: Result = match result { + Ok(res) => match res.active() { + true if res.sub().is_some() => Ok(res.into()), + false => Err(IntrospectionGuardError::Inactive), + _ => Err(IntrospectionGuardError::NoUserId), + }, + Err(source) => return Err(IntrospectionGuardError::Introspection { source }), + }; + user + }; + + wrap_future(fut).await + } +} + +impl IntrospectedUser { + fn token_from_header(parts: &mut Parts) -> Result { + let auth_header = parts + .headers + .get("Authorization") + .ok_or(IntrospectionGuardError::InvalidHeader) + .unwrap(); + + let auth_str = auth_header + .to_str() + .map_err(|_| IntrospectionGuardError::InvalidHeader) + .unwrap(); + + if !auth_str.starts_with("Bearer ") { + return Err(IntrospectionGuardError::WrongScheme); + } + + let token = auth_str.trim_start_matches("Bearer ").trim().to_string(); + Ok(token) + } +} + +impl From for IntrospectedUser { + fn from(response: ZitadelIntrospectionResponse) -> Self { + Self { + user_id: response.sub().unwrap().to_string(), + username: response.username().map(|s| s.to_string()), + name: response.extra_fields().name.clone(), + given_name: response.extra_fields().given_name.clone(), + family_name: response.extra_fields().family_name.clone(), + preferred_username: response.extra_fields().preferred_username.clone(), + email: response.extra_fields().email.clone(), + email_verified: response.extra_fields().email_verified, + locale: response.extra_fields().locale.clone(), + project_roles: response.extra_fields().project_roles.clone(), + metadata: response.extra_fields().metadata.clone(), + } + } +} + +#[cfg(test)] +mod tests { + #![allow(clippy::all)] + + use axum::body::Body; + use axum::http::{Request, StatusCode}; + use axum::response::IntoResponse; + use axum::routing::get; + use axum::Router; + + use tower::ServiceExt; + + use crate::axum_introspector::introspection::{IntrospectionState, IntrospectionStateBuilder}; + use crate::credentials::Application; + + use super::*; + + const ZITADEL_URL: &str = "https://zitadel-libraries-l8boqa.zitadel.cloud"; + const PERSONAL_ACCESS_TOKEN: &str = + "dEnGhIFs3VnqcQU5D2zRSeiarB1nwH6goIKY0J8MWZbsnWcTuu1C59lW9DgCq1y096GYdXA"; + const APPLICATION: &str = r#" + { + "type": "application", + "keyId": "181963758610940161", + "key": "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAwT2YZJytkkZ1DDM3dcu1OA8YPzHu6XR8HotdMNRnV75GhOT4\nB7zDtdtoP8w/1NHHPEJ859e0kYhrrnKikOKLS6fS1KRsmqR5ZvTq8SlZ2mq3RcX2\nebZx5dQt36INij/WXdsBmjM/yfWvqqWBSb0L/186DaWwmmIxoXWe873vxRmlzblg\nGd8Nu07s9YTREbGPbtFVHEUM6xI4oIe8HJ0e1+JBkiGqk31Cogo0FoAxrOAg0Sf4\n5XiUMYIjzqh8673F9SC4IpVxG22mpFk3vDFuAITaStWYbiH2hPJNKWyX9HDCZb1D\nDqa3wZBDiLqWxh22hNZ6ZIe+3UoSGWsPBH+E1wIDAQABAoIBAD2v5QsRPRN57HmF\njAnNir8nimz6CrN53Pl/MbOZypenBSn9UfReXPeb3+6lzCarBPgGnYsBQAJJU16v\n95daym7PVy1Mg+Ll6F9mhe2Qbr+b23+pj2IRTNC6aB6Aw+PDNzJk7GEGRTG6fWZz\nSQ96Cu9tvcGHiBXwjLlnK+PRWU5IsCiLsjT4xBXsMLMw3YOdMK5z58sqr+SnNEyq\nRHoEvi9aC94WrargVB45Yx+81YNW8uQ5rMDmYaJC5a7ENz522SlAuf4T+fAGJ/HE\n/qbZGD4YwlLqAFDgewQ+5tEWEus3zgY2MIR7vN2zXU1Ptk+mQkXZl/Pxdp7q1xU+\nvr/kcykCgYEAy7MiIAzc1ctQDvkk3HiespzdQ/sC7+CGsBzkyubRc9Oq/YR7GfVK\nGTuDEDlWwx92VAvJGDWRa3T426YDyqiPj66uo836sgL15Uigg5afZun2bqGC78le\nBhSy9b+0YDHPa87GxtKt9UmMoB6WdmoPzOkLEEGS7eesmk2DDgY+QSUCgYEA8tr/\n3PawigL1cxuFpcO1lH6XUspGeAo5yB8FXvfW5g50e37LgooIvOFgUlYuchxwr6uh\nW+CUAWmm4farsgvMBMPYw+PbkCTi/xemiiDmMHUYd7sJkTl0JXApq3pZsNMg4Fw/\n29RynmcG8TGe2dkwrWp1aBYjvIHwEHuNHHTTA0sCgYBtSUFAwsXkaj0cm2y8YHZ8\nS46mv1AXFHYOnKHffjDXnLN7ao2FIsXLfdNWa/zxmLqqYtxUAcFwToSJi6szGnZT\nVxvZRFSBFveIOQvtLW1+EH4nYr3WGko4pvhQwrZqea7YH0skNrogBILPEToWc9bg\nUBOgeB31R7uh2X47kvvphQKBgQDWc60dYnniZVp5mwQZrQjbaC4YXaZ8ugrsPPhx\nNEoAPSN/KihrzZiJsjtsec3p1lNrzRNgHqCT3sgPIdPcFa7DRm5UDRIF54zL1gaq\nUwLyJ3TDxdZc928o4DLryc8J5mZRuSRq6t+MIU5wDnFHzhK+EBQ9Jc/I1rU22ONz\nDXaIoQKBgH14Apggo0o4Eo+OnEBRFbbDulaOfVLPTK9rktikbwO1vzDch8kdcwCU\nsvtRXHjDQL93Ih/8S9aDJZoSDulwr3VUsuDiDEb4jfYmP2sbNO4nIJt+SBMhVOXV\nt7E/uWK28X0GL/bIUzSMMgTfdjhXEtJW+s6hQU1fG+9U1qVTQ2R/\n-----END RSA PRIVATE KEY-----\n", + "appId": "181963751145079041", + "clientId": "181963751145144577@zitadel_rust_test" + }"#; + + async fn authed(user: IntrospectedUser) -> impl IntoResponse { + format!( + "Hello authorized user: {:?} with id {}", + user.username, user.user_id + ) + } + + async fn unauthed() -> impl IntoResponse { + "Hello unauthorized" + } + + #[derive(Clone)] + struct SomeUserState { + introspection_state: IntrospectionState, + } + + impl FromRef for IntrospectionState { + fn from_ref(input: &SomeUserState) -> Self { + input.introspection_state.clone() + } + } + + async fn app() -> Router { + let introspection_state = IntrospectionStateBuilder::new(ZITADEL_URL) + .with_jwt_profile(Application::load_from_json(APPLICATION).unwrap()) + .build() + .await + .unwrap(); + + let state = SomeUserState { + introspection_state, + }; + + let app = Router::new() + .route("/unauthed", get(unauthed)) + .route("/authed", get(authed)) + .with_state(state); + + return app; + } + + #[tokio::test] + async fn can_guard() { + let app = app().await; + + let resp = app + .oneshot( + Request::builder() + .uri("/authed") + .body(Body::empty()) + .unwrap(), + ) + .await + .unwrap(); + + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + } + + #[tokio::test] + async fn guard_protects_if_non_bearer_present() { + let app = app().await; + + let resp = app + .oneshot( + Request::builder() + .uri("/authed") + .header("authorization", "Basic Something") + .body(Body::empty()) + .unwrap(), + ) + .await + .unwrap(); + + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + } + + #[tokio::test] + async fn guard_protects_if_multiple_auth_headers_present() { + let app = app().await; + + let resp = app + .oneshot( + Request::builder() + .uri("/authed") + .header("authorization", "something one") + .header("authorization", "something two") + .body(Body::empty()) + .unwrap(), + ) + .await + .unwrap(); + + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + } + + #[tokio::test] + async fn guard_protects_if_invalid_token() { + let app = app().await; + + let resp = app + .oneshot( + Request::builder() + .uri("/authed") + .header("authorization", "Bearer something") + .body(Body::empty()) + .unwrap(), + ) + .await + .unwrap(); + + assert_eq!(resp.status(), StatusCode::FORBIDDEN); + } + + #[tokio::test] + async fn guard_allows_valid_token() { + let app = app().await; + + let resp = app + .oneshot( + Request::builder() + .uri("/authed") + .header("authorization", format!("Bearer {PERSONAL_ACCESS_TOKEN}")) + .body(Body::empty()) + .unwrap(), + ) + .await + .unwrap(); + + assert_eq!(resp.status(), StatusCode::OK); + } + + // #[cfg(feature = "introspection_cache")] + mod introspection_cache { + use super::*; + use crate::oidc::introspection::cache::in_memory::InMemoryIntrospectionCache; + use crate::oidc::introspection::cache::IntrospectionCache; + use crate::oidc::introspection::ZitadelIntrospectionExtraTokenFields; + use chrono::{TimeDelta, Utc}; + use http_body_util::BodyExt; + use std::ops::Add; + use std::sync::Arc; + + async fn app_witch_cache(cache: impl IntrospectionCache + 'static) -> Router { + let introspection_state = IntrospectionStateBuilder::new(ZITADEL_URL) + .with_jwt_profile(Application::load_from_json(APPLICATION).unwrap()) + .with_introspection_cache(cache) + .build() + .await + .unwrap(); + + let state = SomeUserState { + introspection_state, + }; + + let app = Router::new() + .route("/unauthed", get(unauthed)) + .route("/authed", get(authed)) + .with_state(state); + + return app; + } + + #[tokio::test] + async fn guard_uses_cached_response() { + let cache = Arc::new(InMemoryIntrospectionCache::default()); + let app = app_witch_cache(cache.clone()).await; + + let mut res = ZitadelIntrospectionResponse::new( + true, + ZitadelIntrospectionExtraTokenFields::default(), + ); + res.set_sub(Some("cached_sub".to_string())); + res.set_exp(Some(Utc::now().add(TimeDelta::days(1)))); + cache.set(PERSONAL_ACCESS_TOKEN, res).await; + + let response = app + .oneshot( + Request::builder() + .uri("/authed") + .header("authorization", format!("Bearer {PERSONAL_ACCESS_TOKEN}")) + .body(Body::empty()) + .unwrap(), + ) + .await + .unwrap(); + + let text = String::from_utf8( + response + .into_body() + .collect() + .await + .unwrap() + .to_bytes() + .to_vec(), + ) + .unwrap(); + assert!(text.contains("cached_sub")); + } + + #[tokio::test] + async fn guard_caches_response() { + let cache = Arc::new(InMemoryIntrospectionCache::default()); + let app = app_witch_cache(cache.clone()).await; + + let response = app + .oneshot( + Request::builder() + .uri("/authed") + .header("authorization", format!("Bearer {PERSONAL_ACCESS_TOKEN}")) + .body(Body::empty()) + .unwrap(), + ) + .await + .unwrap(); + + let text = String::from_utf8( + response + .into_body() + .collect() + .await + .unwrap() + .to_bytes() + .to_vec(), + ) + .unwrap(); + + let cached_response = cache.get(PERSONAL_ACCESS_TOKEN).await.unwrap(); + + assert!(text.contains(cached_response.sub().unwrap())); + } + } +} diff --git a/src/axum_introspector/mod.rs b/src/axum_introspector/mod.rs new file mode 100644 index 0000000..0fbe536 --- /dev/null +++ b/src/axum_introspector/mod.rs @@ -0,0 +1 @@ +pub mod introspection; \ No newline at end of file diff --git a/src/credentials/application.rs b/src/credentials/application.rs new file mode 100644 index 0000000..2befa8c --- /dev/null +++ b/src/credentials/application.rs @@ -0,0 +1,116 @@ +use custom_error::custom_error; +use jsonwebtoken::{encode, Algorithm, EncodingKey, Header}; +use serde::{Deserialize, Serialize}; +use std::fs::read_to_string; +use std::path::Path; + +use crate::credentials::jwt::JwtClaims; + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Application { + client_id: String, + app_id: String, + key_id: String, + key: String, +} + +custom_error! { + pub ApplicationError + Io{source: std::io::Error} = "unable to read from file: {source}", + Json{source: serde_json::Error} = "could not parse json: {source}", + Key{source: jsonwebtoken::errors::Error} = "could not parse RSA key: {source}", +} + +impl Application { + pub fn load_from_file>(file_path: P) -> Result { + let data = read_to_string(file_path).map_err(|e| ApplicationError::Io { source: e })?; + Application::load_from_json(data.as_str()) + } + + pub fn load_from_json(json: &str) -> Result { + let sa: Application = + serde_json::from_str(json).map_err(|e| ApplicationError::Json { source: e })?; + Ok(sa) + } + + pub fn create_signed_jwt(&self, audience: &str) -> Result { + let key = EncodingKey::from_rsa_pem(self.key.as_bytes()) + .map_err(|e| ApplicationError::Key { source: e })?; + let mut header = Header::new(Algorithm::RS256); + header.kid = Some(self.key_id.to_string()); + let claims = JwtClaims::new(&self.client_id, audience); + let jwt = encode(&header, &claims, &key)?; + + Ok(jwt) + } +} + +#[cfg(test)] +mod tests { + #![allow(clippy::all)] + + use std::fs::File; + use std::io::Write; + + use super::*; + + const APPLICATION: &str = r#" + { + "type": "application", + "keyId": "181963758610940161", + "key": "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAwT2YZJytkkZ1DDM3dcu1OA8YPzHu6XR8HotdMNRnV75GhOT4\nB7zDtdtoP8w/1NHHPEJ859e0kYhrrnKikOKLS6fS1KRsmqR5ZvTq8SlZ2mq3RcX2\nebZx5dQt36INij/WXdsBmjM/yfWvqqWBSb0L/186DaWwmmIxoXWe873vxRmlzblg\nGd8Nu07s9YTREbGPbtFVHEUM6xI4oIe8HJ0e1+JBkiGqk31Cogo0FoAxrOAg0Sf4\n5XiUMYIjzqh8673F9SC4IpVxG22mpFk3vDFuAITaStWYbiH2hPJNKWyX9HDCZb1D\nDqa3wZBDiLqWxh22hNZ6ZIe+3UoSGWsPBH+E1wIDAQABAoIBAD2v5QsRPRN57HmF\njAnNir8nimz6CrN53Pl/MbOZypenBSn9UfReXPeb3+6lzCarBPgGnYsBQAJJU16v\n95daym7PVy1Mg+Ll6F9mhe2Qbr+b23+pj2IRTNC6aB6Aw+PDNzJk7GEGRTG6fWZz\nSQ96Cu9tvcGHiBXwjLlnK+PRWU5IsCiLsjT4xBXsMLMw3YOdMK5z58sqr+SnNEyq\nRHoEvi9aC94WrargVB45Yx+81YNW8uQ5rMDmYaJC5a7ENz522SlAuf4T+fAGJ/HE\n/qbZGD4YwlLqAFDgewQ+5tEWEus3zgY2MIR7vN2zXU1Ptk+mQkXZl/Pxdp7q1xU+\nvr/kcykCgYEAy7MiIAzc1ctQDvkk3HiespzdQ/sC7+CGsBzkyubRc9Oq/YR7GfVK\nGTuDEDlWwx92VAvJGDWRa3T426YDyqiPj66uo836sgL15Uigg5afZun2bqGC78le\nBhSy9b+0YDHPa87GxtKt9UmMoB6WdmoPzOkLEEGS7eesmk2DDgY+QSUCgYEA8tr/\n3PawigL1cxuFpcO1lH6XUspGeAo5yB8FXvfW5g50e37LgooIvOFgUlYuchxwr6uh\nW+CUAWmm4farsgvMBMPYw+PbkCTi/xemiiDmMHUYd7sJkTl0JXApq3pZsNMg4Fw/\n29RynmcG8TGe2dkwrWp1aBYjvIHwEHuNHHTTA0sCgYBtSUFAwsXkaj0cm2y8YHZ8\nS46mv1AXFHYOnKHffjDXnLN7ao2FIsXLfdNWa/zxmLqqYtxUAcFwToSJi6szGnZT\nVxvZRFSBFveIOQvtLW1+EH4nYr3WGko4pvhQwrZqea7YH0skNrogBILPEToWc9bg\nUBOgeB31R7uh2X47kvvphQKBgQDWc60dYnniZVp5mwQZrQjbaC4YXaZ8ugrsPPhx\nNEoAPSN/KihrzZiJsjtsec3p1lNrzRNgHqCT3sgPIdPcFa7DRm5UDRIF54zL1gaq\nUwLyJ3TDxdZc928o4DLryc8J5mZRuSRq6t+MIU5wDnFHzhK+EBQ9Jc/I1rU22ONz\nDXaIoQKBgH14Apggo0o4Eo+OnEBRFbbDulaOfVLPTK9rktikbwO1vzDch8kdcwCU\nsvtRXHjDQL93Ih/8S9aDJZoSDulwr3VUsuDiDEb4jfYmP2sbNO4nIJt+SBMhVOXV\nt7E/uWK28X0GL/bIUzSMMgTfdjhXEtJW+s6hQU1fG+9U1qVTQ2R/\n-----END RSA PRIVATE KEY-----\n", + "appId": "181963751145079041", + "clientId": "181963751145144577@zitadel_rust_test" + }"#; + + #[test] + fn load_successfully_from_json() { + let sa = Application::load_from_json(APPLICATION).unwrap(); + + assert_eq!(sa.client_id, "181963751145144577@zitadel_rust_test"); + assert_eq!(sa.key_id, "181963758610940161"); + } + + #[test] + fn load_successfully_from_file() { + let mut file = File::create("./temp_app").unwrap(); + file.write_all(APPLICATION.as_bytes()) + .expect("Could not write temp."); + + let sa = Application::load_from_file("./temp_app").unwrap(); + + assert_eq!(sa.client_id, "181963751145144577@zitadel_rust_test"); + assert_eq!(sa.key_id, "181963758610940161"); + } + + #[test] + fn load_faulty_from_json() { + let err = Application::load_from_json("{1234}").unwrap_err(); + + if let ApplicationError::Json { source: _ } = err { + assert!(true); + } else { + assert!(false); + } + } + + #[test] + fn load_faulty_from_file() { + let err = Application::load_from_file("./foobar").unwrap_err(); + + if let ApplicationError::Io { source: _ } = err { + assert!(true); + } else { + assert!(false); + } + } + + #[test] + fn creates_a_signed_jwt() { + let sa = Application::load_from_json(APPLICATION).unwrap(); + let claims = sa.create_signed_jwt("https://zitadel.cloud").unwrap(); + + assert_eq!(&claims[0..5], "eyJ0e"); + } +} diff --git a/src/credentials/jwt.rs b/src/credentials/jwt.rs new file mode 100644 index 0000000..43cfda9 --- /dev/null +++ b/src/credentials/jwt.rs @@ -0,0 +1,24 @@ +use serde::Serialize; + +#[derive(Debug, Serialize)] +pub(super) struct JwtClaims { + iss: String, + sub: String, + iat: i64, + exp: i64, + aud: String, +} + +impl JwtClaims { + pub(super) fn new(sub_and_iss: &str, audience: &str) -> Self { + let iat = time::OffsetDateTime::now_utc(); + let exp = iat + time::Duration::hours(1); + Self { + iss: sub_and_iss.to_string(), + sub: sub_and_iss.to_string(), + iat: iat.unix_timestamp(), + exp: exp.unix_timestamp(), + aud: audience.to_string(), + } + } +} diff --git a/src/credentials/mod.rs b/src/credentials/mod.rs new file mode 100644 index 0000000..b3d0689 --- /dev/null +++ b/src/credentials/mod.rs @@ -0,0 +1,6 @@ +mod application; +mod jwt; +mod service_account; + +pub use application::*; +pub use service_account::*; diff --git a/src/credentials/service_account.rs b/src/credentials/service_account.rs new file mode 100644 index 0000000..1a11f4e --- /dev/null +++ b/src/credentials/service_account.rs @@ -0,0 +1,232 @@ +use custom_error::custom_error; +use jsonwebtoken::{encode, Algorithm, EncodingKey, Header}; +use openidconnect::{ + core::{CoreProviderMetadata, CoreTokenType}, + http::HeaderMap, + reqwest::async_http_client, + EmptyExtraTokenFields, HttpRequest, IssuerUrl, OAuth2TokenResponse, StandardTokenResponse, +}; +use reqwest::{ + header::{ACCEPT, CONTENT_TYPE}, + Method, Url, +}; +use serde::{Deserialize, Serialize}; +use std::fs::read_to_string; + +use crate::credentials::jwt::JwtClaims; + + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ServiceAccount { + user_id: String, + key_id: String, + key: String, +} + + +#[derive(Clone, Debug, Default)] +pub struct AuthenticationOptions { + pub api_access: bool, + + pub scopes: Vec, + + pub roles: Vec, + + pub project_audiences: Vec, +} + +custom_error! { + pub ServiceAccountError + Io{source: std::io::Error} = "unable to read from file: {source}", + Json{source: serde_json::Error} = "could not parse json: {source}", + Key{source: jsonwebtoken::errors::Error} = "could not parse RSA key: {source}", + AudienceUrl{source: openidconnect::url::ParseError} = "audience url could not be parsed: {source}", + DiscoveryError{source: Box} = "could not discover OIDC document: {source}", + TokenEndpointMissing = "OIDC document does not contain token endpoint", + HttpError{source: openidconnect::reqwest::Error} = "http error: {source}", + UrlEncodeError = "could not encode url params for token request", + TokenError = "could not fetch token from endpoint", + AccessTokenMissing = "token response does not contain access token", +} + +impl ServiceAccount { + pub fn load_from_file(file_path: &str) -> Result { + let data = read_to_string(file_path).map_err(|e| ServiceAccountError::Io { source: e })?; + ServiceAccount::load_from_json(data.as_str()) + } + + pub fn load_from_json(json: &str) -> Result { + let sa: ServiceAccount = + serde_json::from_str(json).map_err(|e| ServiceAccountError::Json { source: e })?; + Ok(sa) + } + + pub async fn authenticate(&self, audience: &str) -> Result { + self.authenticate_with_options(audience, &Default::default()) + .await + } + + pub async fn authenticate_with_options( + &self, + audience: &str, + options: &AuthenticationOptions, + ) -> Result { + let issuer = IssuerUrl::new(audience.to_string()) + .map_err(|e| ServiceAccountError::AudienceUrl { source: e })?; + let metadata = CoreProviderMetadata::discover_async(issuer, async_http_client) + .await + .map_err(|e| ServiceAccountError::DiscoveryError { + source: Box::new(e), + })?; + + let jwt = self.create_signed_jwt(audience)?; + let url = metadata + .token_endpoint() + .ok_or(ServiceAccountError::TokenEndpointMissing)?; + let mut headers = HeaderMap::new(); + headers.append(ACCEPT, "application/json".parse().unwrap()); + headers.append( + CONTENT_TYPE, + "application/x-www-form-urlencoded".parse().unwrap(), + ); + let body = serde_urlencoded::to_string([ + ("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer"), + ("assertion", &jwt), + ("scope", &options.create_scopes()), + ]) + .map_err(|_| ServiceAccountError::UrlEncodeError)?; + + let url = + Url::parse(url.as_str()).map_err(|_| ServiceAccountError::TokenEndpointMissing)?; + let response = async_http_client(HttpRequest { + url, + method: Method::POST, + headers, + body: body.into_bytes(), + }) + .await + .map_err(|e| ServiceAccountError::HttpError { source: e })?; + + serde_json::from_slice(response.body.as_slice()) + .map_err(|e| ServiceAccountError::Json { source: e }) + .map( + |response: StandardTokenResponse| { + response.access_token().secret().clone() + }, + ) + } + + fn create_signed_jwt(&self, audience: &str) -> Result { + let key = EncodingKey::from_rsa_pem(self.key.as_bytes()) + .map_err(|e| ServiceAccountError::Key { source: e })?; + let mut header = Header::new(Algorithm::RS256); + header.kid = Some(self.key_id.to_string()); + let claims = JwtClaims::new(&self.user_id, audience); + let jwt = encode(&header, &claims, &key)?; + + Ok(jwt) + } +} + +impl AuthenticationOptions { + fn create_scopes(&self) -> String { + let mut result = vec!["openid".to_string()]; + + for role in &self.roles { + let scope = format!("urn:zitadel:iam:org:project:role:{}", role); + if !result.contains(&scope) { + result.push(scope); + } + } + + for p_id in &self.project_audiences { + let scope = format!("urn:zitadel:iam:org:project:id:{}:aud", p_id); + if !result.contains(&scope) { + result.push(scope); + } + } + + for scope in &self.scopes { + if !result.contains(scope) { + result.push(scope.clone()); + } + } + + let api_scope = "urn:zitadel:iam:org:project:id:zitadel:aud".to_string(); + if self.api_access && !result.contains(&api_scope) { + result.push(api_scope); + } + + result.join(" ") + } +} + +#[cfg(test)] +mod tests { + #![allow(clippy::all)] + + use std::fs::File; + use std::io::Write; + + use super::*; + + const ZITADEL_URL: &str = "https://zitadel-libraries-l8boqa.zitadel.cloud"; + const SERVICE_ACCOUNT: &str = r#" + { + "type": "serviceaccount", + "keyId": "181828078849229057", + "key": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpQIBAAKCAQEA9VIWALQqzx1ypi42t7MG4KSOMldD10brsEUjTcjqxhl6TJrP\nsjaNKWArnV/XH+6ZKRd55mUEFFx9VflqdwQtMVPjZKXpV4cFDiPwf1Z1h1DS6im4\nSo7eKR7OGb7TLBhwt7i2UPF4WnxBhTp/M6pG5kCJ1t8glIo5yRbrILXObRmvNWMz\nVIFAyw68NDZGYNhnR8AT43zjeJTFXG/suuEoXO/mMmMjsYY8kS0BbiQeq5t5hIrr\na/odswkDPn5Zd4P91iJHDnYlgfJuo3oRmgpOj/dDsl+vTol+vveeMO4TXPwZcl36\ngUNPok7nd6BA3gqmOS+fMImzmZB42trghARXXwIDAQABAoIBAQCbMOGQcml+ep+T\ntzqQPWYFaLQ37nKRVmE1Mpeh1o+G4Ik4utrXX6EvYpJUzVN29ObZUuufr5nEE7qK\nT+1k+zRntyzr9/VElLrC9kNnGtfg0WWMEvZt3DF4i+9P5CMNCy0LXIOhcxBzFZYR\nZS8hDQArGvrX/nFK5qKlrqTyHXFIHDFa6z59ErhXEnsTgRvx/Mo+6UkdBkHsKnlJ\nAbXqXFbfz6nDsF1DgRra5ODn1k8nZqnC/YcssE7/dlbuByz10ECkOSzqYcfufnsb\n9N1Ld4Xlj3yzsqPFzEJyHHm9eEHQXsPavaXiM64/+zpsksLscEIE/0KtIy5tngpZ\nSCqZAcj5AoGBAPb1bQFWUBmmUuSTtSymsxgXghJiJ3r+jJgdGbkv2IsRTs4En5Sz\n0SbPE1YWmMDDgTacJlB4/XiaojQ/j1EEY17inxYomE72UL6/ET7ycsEw3e9ALuD5\np0y2Sdzes2biH30bw5jD8kJ+hV18T745KtzrwSH4I0lAjnkmiH+0S67VAoGBAP5N\nTtAp/Qdxh9GjNSw1J7KRLtJrrr0pPrJ9av4GoFoWlz+Qw2X3dl8rjG3Bqz9LPV7A\ngiHMel8WTmdIM/S3F4Q3ufEfE+VzG+gncWd9SJfX5/LVhatPzTGLNsY7AYGEpSwT\n5/0anS1mHrLwsVcPrZnigekr5A5mfZl6nxtOnE9jAoGBALACqacbUkmFrmy1DZp+\nUQSptI3PoR3bEG9VxkCjZi1vr3/L8cS1CCslyT1BK6uva4d1cSVHpjfv1g1xA38V\nppE46XOMiUk16sSYPv1jJQCmCHd9givcIy3cefZOTwTTwueTAyv888wKipjfgaIs\n8my0JllEljmeJi0Ylo6V/J7lAoGBAIFqRlmZhLNtC3mcXUsKIhG14OYk9uA9RTMA\nsJpmNOSj6oTm3wndTdhRCT4x+TxUxf6aaZ9ZuEz7xRq6m/ZF1ynqUi5ramyyj9kt\neYD5OSBNODVUhJoSGpLEDjQDg1iucIBmAQHFsYeRGL5nz1hHGkneA87uDzlk3zZk\nOORktReRAoGAGUfU2UfaniAlqrZsSma3ZTlvJWs1x8cbVDyKTYMX5ShHhp+cA86H\nYjSSol6GI2wQPP+qIvZ1E8XyzD2miMJabl92/WY0tHejNNBEHwD8uBZKrtMoFWM7\nWJNl+Xneu/sT8s4pP2ng6QE7jpHXi2TUNmSlgQry9JN2AmA9TuSTW2Y=\n-----END RSA PRIVATE KEY-----\n", + "userId": "181828061098934529" + }"#; + + #[test] + fn load_successfully_from_json() { + let sa = ServiceAccount::load_from_json(SERVICE_ACCOUNT).unwrap(); + + assert_eq!(sa.user_id, "181828061098934529"); + assert_eq!(sa.key_id, "181828078849229057"); + } + + #[test] + fn load_successfully_from_file() { + let mut file = File::create("./temp_sa").unwrap(); + file.write_all(SERVICE_ACCOUNT.as_bytes()) + .expect("Could not write temp."); + + let sa = ServiceAccount::load_from_file("./temp_sa").unwrap(); + + assert_eq!(sa.user_id, "181828061098934529"); + assert_eq!(sa.key_id, "181828078849229057"); + } + + #[test] + fn load_faulty_from_json() { + let err = ServiceAccount::load_from_json("{1234}").unwrap_err(); + + if let ServiceAccountError::Json { source: _ } = err { + assert!(true); + } else { + assert!(false); + } + } + + #[test] + fn load_faulty_from_file() { + let err = ServiceAccount::load_from_file("./foobar").unwrap_err(); + + if let ServiceAccountError::Io { source: _ } = err { + assert!(true); + } else { + assert!(false); + } + } + + #[test] + fn creates_a_signed_jwt() { + let sa = ServiceAccount::load_from_json(SERVICE_ACCOUNT).unwrap(); + let claims = sa.create_signed_jwt(ZITADEL_URL).unwrap(); + + assert_eq!(&claims[0..5], "eyJ0e"); + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..152e986 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,280 @@ +mod api; +mod axum_introspector; +mod credentials; +mod oidc; +mod session_storage; +mod utilities; +mod zitadel_http; + +use crate::api::authenticated::AuthenticatedApi; +use crate::api::public::PublicApi; +use crate::axum_introspector::introspection::{ + IntrospectedUser, IntrospectionState, IntrospectionStateBuilder, +}; +use crate::oidc::introspection::cache::cloudflare::CloudflareIntrospectionCache; +use crate::session_storage::cloudflare::CloudflareKvStore; +use axum::extract::FromRef; +use axum::response::{IntoResponse, Redirect}; +use axum::routing::{any, get}; +use axum::{Router, ServiceExt}; +use bytes::Bytes; +use http::HeaderName; +use serde::{Deserialize, Serialize}; +use serde_json::to_string; +use std::fmt::Debug; +use std::iter::once; +use std::ops::Deref; +use tower::ServiceExt as TowerServiceExt; +use tower_cookies::cookie::SameSite; +use tower_cookies::CookieManagerLayer; +use tower_http::cors::CorsLayer; +use tower_http::propagate_header::PropagateHeaderLayer; +use tower_http::sensitive_headers::SetSensitiveRequestHeadersLayer; +use tower_service::Service; +use tower_sessions::cookie::Key; +use tower_sessions::SessionManagerLayer; +use tower_sessions_core::Expiry; +use tracing::instrument::WithSubscriber; +use tracing_subscriber::prelude::*; +use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; +use worker::*; + +#[event(start)] +fn start() { + let fmt_layer = tracing_subscriber::fmt::layer() + .json() + .without_time() + .with_ansi(false) // Only partially supported across JavaScript runtimes + .with_timer(tracing_subscriber::fmt::time::UtcTime::rfc_3339()); // std::time is not available in browsers + let perf_layer = tracing_web::performance_layer(); + + tracing_subscriber::registry() + .with(fmt_layer) + .with(perf_layer) + .init() +} + +const SIGNING_KEY: &str = "keystore::sig"; +const ENCRYPTION_KEY: &str = "keystore::enc"; + +// main entrypoint + +#[event(fetch)] +async fn fetch( + req: HttpRequest, + _env: Env, + _ctx: Context, +) -> Result> { + console_error_panic_hook::set_once(); + + Ok(route(req, _env).await) +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +struct Callback { + code: String, + state: String, +} + +#[derive(Clone)] +struct AppState { + introspection_state: IntrospectionState, + env: Env, + session_store: CloudflareKvStore, +} +impl FromRef for IntrospectionState { + fn from_ref(input: &AppState) -> Self { + input.introspection_state.clone() + } +} + +async fn route(req: HttpRequest, _env: Env) -> axum_core::response::Response { + let kv = _env.kv("KV_STORAGE").unwrap(); + let cache = CloudflareIntrospectionCache::new(kv.clone()); + + let introspection_state = IntrospectionStateBuilder::new( + _env.secret("AUTH_SERVER_URL") + .unwrap() + .to_string() + .as_str(), + ) + .with_basic_auth( + _env.secret("CLIENT_ID") + .unwrap() + .to_string() + .as_str(), + _env.secret("CLIENT_SECRET") + .unwrap() + .to_string() + .as_str(), + ) + .with_introspection_cache(cache) + .build() + .await + .unwrap(); + + let session_store = CloudflareKvStore::new(kv.clone()); + + let state = AppState { + introspection_state, + session_store: session_store.clone(), + env: _env.clone(), + }; + + let dev_mode = _env.var("DEV_MODE").unwrap().to_string(); // Example check + + let is_dev = dev_mode == "true"; + + let keystore = _env.kv("KV_STORAGE").unwrap(); + + let signing = if let Some(bytes) = keystore.get(SIGNING_KEY).bytes().await.unwrap() { + Key::derive_from(bytes.as_slice()) + } else { + let key = Key::generate(); + keystore + .put_bytes(SIGNING_KEY, key.master()) + .unwrap() + .execute() + .await + .unwrap(); + key + }; + + let encryption = if let Some(bytes) = keystore.get(ENCRYPTION_KEY).bytes().await.unwrap() { + Key::derive_from(bytes.as_slice()) + } else { + let key = Key::generate(); + keystore + .put_bytes(ENCRYPTION_KEY, key.master()) + .unwrap() + .execute() + .await + .unwrap(); + key + }; + + let host_string = _env.secret("APP_URL").unwrap().to_string().as_str().to_owned(); + + let cookie_host_uri = host_string.parse::().unwrap(); + + let mut cookie_host = cookie_host_uri.authority().unwrap().to_string(); + + if cookie_host.starts_with("localhost:") { + cookie_host = "localhost".to_string(); + } + + let session_layer = SessionManagerLayer::new(state.session_store.clone()) + .with_name("session") + .with_expiry(Expiry::OnSessionEnd) + .with_domain(cookie_host) + .with_same_site(SameSite::Lax) + .with_signed(signing) + .with_private(encryption) + .with_path("/") + .with_secure(!is_dev) + .with_always_save(false); + + async fn handle_introspection_errors( + mut response: axum_core::response::Response, + ) -> axum_core::response::Response { + let x_error_header_value = response + .headers() + .get("x-introspection-error") + .and_then(|header_value| header_value.to_str().ok()); + + // not used but is available + let x_session_header_value = response + .headers() + .get("x-session") + .and_then(|header_value| header_value.to_str().ok()); + + match response.status() { + http::StatusCode::UNAUTHORIZED => { + if let Some(x_error) = x_error_header_value { + if x_error == "unauthorized" { + return Redirect::to("/login").into_response(); + } + } + response + } + http::StatusCode::BAD_REQUEST => { + if let Some(x_error) = x_error_header_value { + if x_error == "invalid schema" + || x_error == "invalid header" + || x_error == "introspection error" + { + return Redirect::to("/login").into_response(); + } + } + response + } + http::StatusCode::FORBIDDEN => { + if let Some(x_error) = x_error_header_value { + if x_error == "user is inactive" { + return Redirect::to("/login").into_response(); + } + } + response + } + http::StatusCode::NOT_FOUND => { + if let Some(x_error) = x_error_header_value { + if x_error == "user was not found" { + return Redirect::to("/login").into_response(); + } + } + response + } + http::StatusCode::INTERNAL_SERVER_ERROR => { + if let Some(x_error) = x_error_header_value { + if x_error == "missing config" { + return Redirect::to("/login").into_response(); + } + } + response + } + _ => response, + } + } + + let mut router = Router::new() + .route("/", any(AuthenticatedApi::proxy)) + .route("/login", get(PublicApi::login_page)) // Add the login page route + .route("/login/callback", get(PublicApi::callback)) + .route("/login/authorize", get(PublicApi::authorize)) + .route("/api/whoami", get(whoami)) + .route("/*path", any(AuthenticatedApi::proxy)) + .layer(PropagateHeaderLayer::new(HeaderName::from_static( + "x-request-id", + ))) + .layer(axum::middleware::map_response(handle_introspection_errors)) + .with_state(state) + .layer(session_layer) + .layer(CookieManagerLayer::new()) + .layer(CorsLayer::very_permissive()) + .layer(SetSensitiveRequestHeadersLayer::new(once( + http::header::AUTHORIZATION, + ))); + + router + .as_service() + .ready() + .await + .unwrap() + .oneshot(req) + .await + .unwrap() +} + +async fn whoami( + session: tower_sessions::Session, + introspected_user: IntrospectedUser, +) -> impl IntoResponse { + console_log!("calling whoami"); + to_string(&introspected_user).unwrap() +} + +impl FromRef for CloudflareKvStore { + fn from_ref(input: &AppState) -> Self { + input.session_store.clone() + } +} diff --git a/src/oidc/discovery.rs b/src/oidc/discovery.rs new file mode 100644 index 0000000..a656e96 --- /dev/null +++ b/src/oidc/discovery.rs @@ -0,0 +1,94 @@ +use custom_error::custom_error; +use openidconnect::reqwest::async_http_client; +use openidconnect::{ + core::{ + CoreAuthDisplay, CoreClaimName, CoreClaimType, CoreClientAuthMethod, CoreGrantType, + CoreJsonWebKey, CoreJsonWebKeyType, CoreJsonWebKeyUse, CoreJweContentEncryptionAlgorithm, + CoreJweKeyManagementAlgorithm, CoreJwsSigningAlgorithm, CoreResponseMode, CoreResponseType, + CoreSubjectIdentifierType, + }, + url, AdditionalProviderMetadata, IntrospectionUrl, IssuerUrl, ProviderMetadata, RevocationUrl, +}; +use serde::{Deserialize, Serialize}; + +custom_error! { + pub DiscoveryError + IssuerUrl{source: url::ParseError} = "could not parse issuer url: {source}", + DiscoveryDocument = "could not discover OIDC document", +} + +pub async fn discover(authority: &str) -> Result { + let issuer = IssuerUrl::new(authority.to_string()) + .map_err(|source| DiscoveryError::IssuerUrl { source })?; + ZitadelProviderMetadata::discover_async(issuer, async_http_client) + .await + .map_err(|_| DiscoveryError::DiscoveryDocument) +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct ZitadelAdditionalMetadata { + pub introspection_endpoint: Option, + pub revocation_endpoint: Option, +} + +impl AdditionalProviderMetadata for ZitadelAdditionalMetadata {} + + +pub type ZitadelProviderMetadata = ProviderMetadata< + ZitadelAdditionalMetadata, + CoreAuthDisplay, + CoreClientAuthMethod, + CoreClaimName, + CoreClaimType, + CoreGrantType, + CoreJweContentEncryptionAlgorithm, + CoreJweKeyManagementAlgorithm, + CoreJwsSigningAlgorithm, + CoreJsonWebKeyType, + CoreJsonWebKeyUse, + CoreJsonWebKey, + CoreResponseMode, + CoreResponseType, + CoreSubjectIdentifierType, +>; + +#[cfg(test)] +mod tests { + #![allow(clippy::all)] + + use super::*; + + const ZITADEL_URL: &str = "https://zitadel-libraries-l8boqa.zitadel.cloud"; + + #[tokio::test] + async fn discovery_fails_with_invalid_url() { + let result = discover("foobar").await; + + assert!(result.is_err()); + assert!(matches!( + result.unwrap_err(), + DiscoveryError::IssuerUrl { .. } + )); + } + + #[tokio::test] + async fn discovery_fails_with_invalid_discovery() { + let result = discover("https://smartive.ch").await; + + assert!(result.is_err()); + assert!(matches!( + result.unwrap_err(), + DiscoveryError::DiscoveryDocument + )); + } + + #[tokio::test] + async fn discovery_succeeds() { + let result = discover(ZITADEL_URL).await.unwrap(); + + assert_eq!( + result.token_endpoint().unwrap().to_string(), + "https://zitadel-libraries-l8boqa.zitadel.cloud/oauth/v2/token".to_string() + ); + } +} diff --git a/src/oidc/introspection/cache/cloudflare.rs b/src/oidc/introspection/cache/cloudflare.rs new file mode 100644 index 0000000..4e55896 --- /dev/null +++ b/src/oidc/introspection/cache/cloudflare.rs @@ -0,0 +1,81 @@ +use std::fmt::{Debug, Formatter}; +use async_trait::async_trait; +// use axum_core::response::IntoResponse; +use openidconnect::TokenIntrospectionResponse; +use crate::oidc::introspection::cache::{IntrospectionCache, Response}; +// use crate::session_storage::cloudflare::CloudflareKvStore; + + +/// for storing introspection results. +pub struct CloudflareIntrospectionCache { + kv: worker::kv::KvStore, +} + +impl CloudflareIntrospectionCache { + /// Creates a new instance of `CloudflareIntrospectionCache` with the given KV namespace. + pub fn new(kv: worker::kv::KvStore) -> Self { + Self { kv } + } +} + +impl std::fmt::Debug for CloudflareIntrospectionCache { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("CloudflareKvStore") + .finish_non_exhaustive() + // Probably want to handle this differently + // .field("kvstore", "KVStorePlaceholder") + } +} + + +fn prefixed_key(token: &str) -> String { + format!("introspectioncache::{}", token) +} + +#[async_trait] +impl IntrospectionCache for CloudflareIntrospectionCache { + async fn get(&self, token: &str) -> Option { + get(self.kv.clone(), token).await + } + + async fn set(&self, token: &str, response: Response) { + // Check if the token is active and has an expiration time + set(self.kv.clone(), token, response).await; + } + + async fn clear(&self) { + wrapped_clear(self.kv.clone()).await + } +} + +#[worker::send] +async fn set(kv: worker::kv::KvStore, token: &str, response: Response) { + if response.active() && response.exp().is_some() { + // Serialize the response to JSON + if let Ok(json) = serde_json::to_string(&response) { + // Set the expiration time + let expiration = response.exp().unwrap(); + // Store the serialized response in the KV store with expiration + kv.put(prefixed_key(token).as_str(), json).unwrap().expiration(expiration.timestamp().unsigned_abs()).execute().await.unwrap_or(()); + } + } +} + + +#[worker::send] +async fn get(kv: worker::kv::KvStore, token: &str) -> Option { + if let Some(data) = kv.get(prefixed_key(token).as_str()).text().await.unwrap_or(None) { + serde_json::from_str(&data).ok() + } else { + None + } +} + +#[worker::send] +async fn wrapped_clear(kv: worker::kv::KvStore) { + let keys = kv.list().execute().await.unwrap().keys; + + for key in keys.iter().filter(|key| key.name.starts_with("introspectioncache::")) { + kv.delete(&key.name).await.unwrap_or(()); + } +} \ No newline at end of file diff --git a/src/oidc/introspection/cache/in_memory.rs b/src/oidc/introspection/cache/in_memory.rs new file mode 100644 index 0000000..43668e5 --- /dev/null +++ b/src/oidc/introspection/cache/in_memory.rs @@ -0,0 +1,132 @@ +use std::collections::HashMap; +use std::sync::Arc; +use tokio::sync::RwLock; + +use openidconnect::TokenIntrospectionResponse; +use time::Duration; + +type Response = super::super::ZitadelIntrospectionResponse; + +#[derive(Debug, Clone)] +pub struct InMemoryIntrospectionCache { + cache: Arc>>, +} + +impl InMemoryIntrospectionCache { + /// Creates a new in memory cache backed by a HashMap. + /// No max capacity limit is enforced, but entries are cleared based on expiry. + pub fn new() -> Self { + Self { + cache: Arc::new(RwLock::new(HashMap::new())), + } + } +} + +impl Default for InMemoryIntrospectionCache { + fn default() -> Self { + Self::new() + } +} + +#[async_trait::async_trait] +impl super::IntrospectionCache for InMemoryIntrospectionCache { + async fn get(&self, token: &str) -> Option { + let mut cache = self.cache.write().await; + match cache.get(token) { + Some((response, expires_at)) + if *expires_at < chrono::Utc::now().timestamp() => { + cache.remove(token); + None + } + Some((response, _)) => Some(response.clone()), + None => None, + } + } + + async fn set(&self, token: &str, response: Response) { + if !response.active() || response.exp().is_none() { + return; + } + let expires_at = response.exp().unwrap().timestamp(); + self.cache.write().await.insert(token.to_string(), (response, expires_at)); + } + + async fn clear(&self) { + self.cache.write().await.clear(); + } +} + +#[cfg(test)] +mod tests { + #![allow(clippy::all)] + + use crate::oidc::introspection::cache::IntrospectionCache; + use chrono::{TimeDelta, Utc}; + + use super::*; + + #[tokio::test] + async fn test_get_set() { + let c = InMemoryIntrospectionCache::new(); + let t = &c as &dyn IntrospectionCache; + + let mut response = Response::new(true, Default::default()); + response.set_exp(Some(Utc::now())); + + t.set("token1", response.clone()).await; + t.set("token2", response.clone()).await; + + assert!(t.get("token1").await.is_some()); + assert!(t.get("token2").await.is_some()); + assert!(t.get("token3").await.is_none()); + } + + #[tokio::test] + async fn test_non_exp_response() { + let c = InMemoryIntrospectionCache::new(); + let t = &c as &dyn IntrospectionCache; + + let response = Response::new(true, Default::default()); + + t.set("token1", response.clone()).await; + t.set("token2", response.clone()).await; + + assert!(t.get("token1").await.is_none()); + assert!(t.get("token2").await.is_none()); + } + + #[tokio::test] + async fn test_clear() { + let c = InMemoryIntrospectionCache::new(); + let t = &c as &dyn IntrospectionCache; + + let mut response = Response::new(true, Default::default()); + response.set_exp(Some(Utc::now())); + + t.set("token1", response.clone()).await; + t.set("token2", response.clone()).await; + + t.clear().await; + + assert!(t.get("token1").await.is_none()); + assert!(t.get("token2").await.is_none()); + } + + #[tokio::test] + async fn test_remove_expired_token() { + let c = InMemoryIntrospectionCache::new(); + let t = &c as &dyn IntrospectionCache; + + let mut response = Response::new(true, Default::default()); + response.set_exp(Some(Utc::now() - TimeDelta::try_seconds(10).unwrap())); + + t.set("token1", response.clone()).await; + t.set("token2", response.clone()).await; + + let _ = t.get("token1").await; + let _ = t.get("token2").await; + + assert!(t.get("token1").await.is_none()); + assert!(t.get("token2").await.is_none()); + } +} \ No newline at end of file diff --git a/src/oidc/introspection/cache/mod.rs b/src/oidc/introspection/cache/mod.rs new file mode 100644 index 0000000..b6bbb2e --- /dev/null +++ b/src/oidc/introspection/cache/mod.rs @@ -0,0 +1,37 @@ +use async_trait::async_trait; +use std::fmt::Debug; +use std::ops::Deref; + +pub mod in_memory; +pub mod cloudflare; + +pub type Response = super::ZitadelIntrospectionResponse; + + +#[async_trait] +pub trait IntrospectionCache: Send + Sync + std::fmt::Debug { + async fn get(&self, token: &str) -> Option; + + async fn set(&self, token: &str, response: Response); + + async fn clear(&self); +} + +#[async_trait] +impl IntrospectionCache for T +where + T: Deref + Send + Sync + Debug, + V: IntrospectionCache, +{ + async fn get(&self, token: &str) -> Option { + self.deref().get(token).await + } + + async fn set(&self, token: &str, response: Response) { + self.deref().set(token, response).await + } + + async fn clear(&self) { + self.deref().clear().await + } +} diff --git a/src/oidc/introspection/mod.rs b/src/oidc/introspection/mod.rs new file mode 100644 index 0000000..f4deefd --- /dev/null +++ b/src/oidc/introspection/mod.rs @@ -0,0 +1,260 @@ +use custom_error::custom_error; +use openidconnect::http::Method; +use openidconnect::reqwest::async_http_client; +use openidconnect::url::{ParseError, Url}; +use openidconnect::HttpResponse; +use openidconnect::{ + core::CoreTokenType, ExtraTokenFields, HttpRequest, StandardTokenIntrospectionResponse, +}; + +use reqwest::header::{HeaderMap, ACCEPT, AUTHORIZATION, CONTENT_TYPE}; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::error::Error; +use std::fmt::{Debug, Display}; +use base64::Engine; +use crate::credentials::{Application, ApplicationError}; + +pub mod cache; + +custom_error! { + pub IntrospectionError + RequestFailed{source: openidconnect::reqwest::Error} = "the introspection request did fail: {source}", + PayloadSerialization = "could not correctly serialize introspection payload", + JWTProfile{source: ApplicationError} = "could not create signed jwt key: {source}", + ParseUrl{source: ParseError} = "could not parse url: {source}", + ParseResponse{source: serde_json::Error} = "could not parse introspection response: {source}", + DecodeResponse{source: base64::DecodeError} = "could not decode base64 metadata: {source}", + ResponseError{source: ZitadelResponseError} = "received error response from Zitadel: {source}", +} + +#[derive(Clone, Debug, Serialize, Deserialize, Default)] +pub struct ZitadelIntrospectionExtraTokenFields { + pub name: Option, + pub given_name: Option, + pub family_name: Option, + pub preferred_username: Option, + pub email: Option, + pub email_verified: Option, + pub locale: Option, + #[serde(rename = "urn:zitadel:iam:user:resourceowner:id")] + pub resource_owner_id: Option, + #[serde(rename = "urn:zitadel:iam:user:resourceowner:name")] + pub resource_owner_name: Option, + #[serde(rename = "urn:zitadel:iam:user:resourceowner:primary_domain")] + pub resource_owner_primary_domain: Option, + #[serde(rename = "urn:zitadel:iam:org:project:roles")] + pub project_roles: Option>>, + #[serde(rename = "urn:zitadel:iam:user:metadata")] + pub metadata: Option>, +} + +impl ExtraTokenFields for ZitadelIntrospectionExtraTokenFields {} + +pub type ZitadelIntrospectionResponse = + StandardTokenIntrospectionResponse; + +#[derive(Debug, Clone)] +pub enum AuthorityAuthentication { + Basic { + client_id: String, + client_secret: String, + }, + JWTProfile { application: Application }, +} + +fn headers(auth: &AuthorityAuthentication) -> HeaderMap { + let mut headers = HeaderMap::new(); + headers.append(ACCEPT, "application/json".parse().unwrap()); + headers.append( + CONTENT_TYPE, + "application/x-www-form-urlencoded".parse().unwrap(), + ); + + match auth { + AuthorityAuthentication::Basic { + client_id, + client_secret, + } => { + headers.append( + AUTHORIZATION, + format!( + "Basic {}", + base64::engine::general_purpose::STANDARD.encode(&format!("{}:{}", client_id, client_secret)) + ) + .parse() + .unwrap(), + ); + headers + } + AuthorityAuthentication::JWTProfile { .. } => headers, + } +} + +fn payload( + authority: &str, + auth: &AuthorityAuthentication, + token: &str, +) -> Result { + match auth { + AuthorityAuthentication::Basic { .. } => serde_urlencoded::to_string([("token", token)]) + .map_err(|_| IntrospectionError::PayloadSerialization), + AuthorityAuthentication::JWTProfile { application } => { + let jwt = application + .create_signed_jwt(authority) + .map_err(|source| IntrospectionError::JWTProfile { source })?; + + serde_urlencoded::to_string([ + ( + "client_assertion_type", + "urn:ietf:params:oauth:client-assertion-type:jwt-bearer", + ), + ("client_assertion", &jwt), + ("token", token), + ]) + .map_err(|_| IntrospectionError::PayloadSerialization) + } + } +} + +pub async fn introspect( + introspection_uri: &str, + authority: &str, + authentication: &AuthorityAuthentication, + token: &str, +) -> Result { + let response = async_http_client(HttpRequest { + url: Url::parse(introspection_uri) + .map_err(|source| IntrospectionError::ParseUrl { source })?, + method: Method::POST, + headers: headers(authentication), + body: payload(authority, authentication, token)?.into_bytes(), + }) + .await + .map_err(|source| IntrospectionError::RequestFailed { source })?; + + if !response.status_code.is_success() { + return Err(IntrospectionError::ResponseError { + source: ZitadelResponseError::from_response(&response), + }); + } + + let mut response: ZitadelIntrospectionResponse = + serde_json::from_slice(response.body.as_slice()) + .map_err(|source| IntrospectionError::ParseResponse { source })?; + decode_metadata(&mut response)?; + Ok(response) +} + +#[derive(Debug)] +struct ZitadelResponseError { + status_code: String, + body: String, +} +impl ZitadelResponseError { + fn from_response(response: &HttpResponse) -> Self { + Self { + status_code: response.status_code.to_string(), + body: String::from_utf8_lossy(response.body.as_slice()).to_string(), + } + } +} +impl Display for ZitadelResponseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "status code: {}, body: {}", self.status_code, self.body) + } +} +impl Error for ZitadelResponseError {} + +// Metadata values are base64 encoded. +fn decode_metadata(response: &mut ZitadelIntrospectionResponse) -> Result<(), IntrospectionError> { + + if let Some(h) = &response.extra_fields().metadata { + let mut extra: ZitadelIntrospectionExtraTokenFields = response.extra_fields().clone(); + let mut metadata = HashMap::new(); + for (k, v) in h { + let decoded_v = base64::engine::general_purpose::STANDARD.decode(v) + .map_err(|source| IntrospectionError::DecodeResponse { source })?; + let decoded_v = String::from_utf8_lossy(&decoded_v).into_owned(); + metadata.insert(k.clone(), decoded_v); + } + extra.metadata.replace(metadata); + response.set_extra_fields(extra) + } + Ok(()) +} + +#[cfg(test)] +mod tests { + #![allow(clippy::all)] + + use crate::oidc::discovery::discover; + use openidconnect::TokenIntrospectionResponse; + + use super::*; + + const ZITADEL_URL: &str = "https://zitadel-libraries-l8boqa.zitadel.cloud"; + const PERSONAL_ACCESS_TOKEN: &str = + "dEnGhIFs3VnqcQU5D2zRSeiarB1nwH6goIKY0J8MWZbsnWcTuu1C59lW9DgCq1y096GYdXA"; + + #[tokio::test] + async fn introspect_fails_with_invalid_url() { + let result = introspect( + "foobar", + "foobar", + &AuthorityAuthentication::Basic { + client_id: "".to_string(), + client_secret: "".to_string(), + }, + "token", + ) + .await; + + assert!(result.is_err()); + assert!(matches!( + result.unwrap_err(), + IntrospectionError::ParseUrl { .. } + )); + } + + #[tokio::test] + async fn introspect_fails_with_invalid_endpoint() { + let meta = discover(ZITADEL_URL).await.unwrap(); + let result = introspect( + &meta.token_endpoint().unwrap().to_string(), + ZITADEL_URL, + &AuthorityAuthentication::Basic { + client_id: "".to_string(), + client_secret: "".to_string(), + }, + "token", + ) + .await; + + assert!(result.is_err()); + } + + #[tokio::test] + async fn introspect_succeeds() { + let meta = discover(ZITADEL_URL).await.unwrap(); + let result = introspect( + &meta + .additional_metadata() + .introspection_endpoint + .as_ref() + .unwrap() + .to_string(), + ZITADEL_URL, + &AuthorityAuthentication::Basic { + client_id: "194339055499018497@zitadel_rust_test".to_string(), + client_secret: "Ip56oGzxKL1rJ8JaleUVKL7qUlpZ1tqHQYRSd6JE1mTlTJ3pDkDzoObHdZsOg88B" + .to_string(), + }, + PERSONAL_ACCESS_TOKEN, + ) + .await + .unwrap(); + + assert!(result.active()); + } +} diff --git a/src/oidc/mod.rs b/src/oidc/mod.rs new file mode 100644 index 0000000..cfdba27 --- /dev/null +++ b/src/oidc/mod.rs @@ -0,0 +1,2 @@ +pub mod discovery; +pub mod introspection; diff --git a/src/session_storage/cloudflare.rs b/src/session_storage/cloudflare.rs new file mode 100644 index 0000000..e7cf0e1 --- /dev/null +++ b/src/session_storage/cloudflare.rs @@ -0,0 +1,201 @@ +use async_trait::async_trait; +use std::fmt::Debug; +use time::OffsetDateTime; +use tower_sessions::{ + session::{Id, Record}, + session_store, SessionStore, +}; +use worker::console_error; +use worker::kv::KvStore; + +#[derive(Clone)] +pub struct CloudflareKvStore { + kv_storage: KvStore, +} + +impl CloudflareKvStore { + pub(crate) fn new(kv_storage: KvStore) -> Self { + Self { kv_storage } + } +} + +impl Default for CloudflareKvStore { + fn default() -> Self { + Self { + kv_storage: KvStore::create("KV_STORAGE").expect("Failed to create KV store"), + } + } +} + +impl std::fmt::Debug for CloudflareKvStore { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("CloudflareKvStore").finish_non_exhaustive() + } +} + +#[worker::send] +async fn get_rec(kv_store: KvStore, session_id: String) -> Option { + match kv_store.get(&session_id).text().await { + Ok(record) => { + serde_json::de::from_str(record.unwrap_or_default().as_str()).unwrap_or_default() + } + Err(err) => { + console_error!("{:?}", err.to_string().as_str()); + None + } + } +} + +#[worker::send] +async fn delete_rec(kv_store: KvStore, session_id: String) -> Option<()> { + kv_store + .delete(&session_id.to_string()) + .await + .expect("Failed to delete session"); + Some(()) +} + +#[worker::send] +async fn create_record_handler(kv_storage: KvStore, record: &mut Record) { + let id = record.id.to_string(); + let serialized_record = serde_json::to_string(record).expect("Failed to serialize record"); + let request = kv_storage + .put(&id, serialized_record) + .expect("Failed to create session"); + if let Err(err) = request.execute().await { + panic!("Failed to execute create request"); + } +} + +#[worker::send] +async fn save_record_handler(kv_storage: KvStore, record: &Record) { + let id = record.id.to_string(); + let serialized_record = serde_json::to_string(record).expect("Failed to serialize record"); + let request = kv_storage.put(&id, serialized_record).unwrap(); + if let Err(err) = request.execute().await { + panic!("Failed to execute save request"); + } +} + +#[async_trait] +impl SessionStore for CloudflareKvStore { + async fn create(&self, record: &mut Record) -> session_store::Result<()> { + if record.id.to_string().is_empty() { + record.id = Id::default(); + } + + create_record_handler(self.kv_storage.clone(), record).await; + + Ok(()) + } + + async fn save(&self, record: &Record) -> session_store::Result<()> { + save_record_handler(self.kv_storage.clone(), record).await; + + Ok(()) + } + + async fn load(&self, session_id: &Id) -> session_store::Result> { + let id = session_id.to_string(); + + match get_rec(self.kv_storage.clone(), id).await { + Some(record) => { + let is_active = is_active(record.expiry_date); + + if is_active { + Ok(Some(record)) + } else { + Ok(None) + } + } + None => Ok(None), + } + } + + async fn delete(&self, session_id: &Id) -> session_store::Result<()> { + delete_rec(self.kv_storage.clone(), session_id.to_string()) + .await + .unwrap(); + + Ok(()) + } +} + +fn is_active(expiry_date: OffsetDateTime) -> bool { + expiry_date > OffsetDateTime::now_utc() +} + +// #[cfg(test)] +// mod tests { +// use time::Duration; +// +// use super::*; +// +// #[tokio::test] +// async fn test_create() { +// let store = CloudflareKvStore::default(); +// let mut record = Record { +// id: Default::default(), +// data: Default::default(), +// expiry_date: OffsetDateTime::now_utc() + Duration::minutes(30), +// }; +// assert!(store.create(&mut record).await.is_ok()); +// } +// +// #[tokio::test] +// async fn test_save() { +// let store = CloudflareKvStore::default(); +// let record = Record { +// id: Default::default(), +// data: Default::default(), +// expiry_date: OffsetDateTime::now_utc() + Duration::minutes(30), +// }; +// assert!(store.save(&record).await.is_ok()); +// } +// +// #[tokio::test] +// async fn test_load() { +// let store = CloudflareKvStore::default(); +// let mut record = Record { +// id: Default::default(), +// data: Default::default(), +// expiry_date: OffsetDateTime::now_utc() + Duration::minutes(30), +// }; +// store.create(&mut record).await.unwrap(); +// let loaded_record = store.load(&record.id).await.unwrap(); +// assert_eq!(Some(record), loaded_record); +// } +// +// #[tokio::test] +// async fn test_delete() { +// let store = CloudflareKvStore::default(); +// let mut record = Record { +// id: Default::default(), +// data: Default::default(), +// expiry_date: OffsetDateTime::now_utc() + Duration::minutes(30), +// }; +// store.create(&mut record).await.unwrap(); +// assert!(store.delete(&record.id).await.is_ok()); +// assert_eq!(None, store.load(&record.id).await.unwrap()); +// } +// +// #[tokio::test] +// async fn test_create_id_collision() { +// let store = CloudflareKvStore::default(); +// let expiry_date = OffsetDateTime::now_utc() + Duration::minutes(30); +// let mut record1 = Record { +// id: Default::default(), +// data: Default::default(), +// expiry_date, +// }; +// let mut record2 = Record { +// id: Default::default(), +// data: Default::default(), +// expiry_date, +// }; +// store.create(&mut record1).await.unwrap(); +// record2.id = record1.id; // Set the same ID for record2 +// store.create(&mut record2).await.unwrap(); +// assert_ne!(record1.id, record2.id); // IDs should be different +// } +// } diff --git a/src/session_storage/in_memory.rs b/src/session_storage/in_memory.rs new file mode 100644 index 0000000..a25602f --- /dev/null +++ b/src/session_storage/in_memory.rs @@ -0,0 +1,134 @@ +use std::{collections::HashMap, sync::Arc}; + +use async_trait::async_trait; +use time::OffsetDateTime; +use tokio::sync::Mutex; +use tower_sessions_core::{ + session::{Id, Record}, + session_store, SessionStore, +}; + +/// A session store that lives only in memory. +/// +/// This is useful for testing but not recommended for real applications. +/// +/// # Examples +/// +/// ```rust +/// use tower_sessions::MemoryStore; +/// MemoryStore::default(); +/// ``` +#[derive(Clone, Debug, Default)] +pub struct MemoryStore(Arc>>); + +#[async_trait] +impl SessionStore for MemoryStore { + async fn create(&self, record: &mut Record) -> session_store::Result<()> { + let mut store_guard = self.0.lock().await; + while store_guard.contains_key(&record.id) { + // Session ID collision mitigation. + record.id = Id::default(); + } + store_guard.insert(record.id, record.clone()); + Ok(()) + } + + async fn save(&self, record: &Record) -> session_store::Result<()> { + self.0.lock().await.insert(record.id, record.clone()); + Ok(()) + } + + async fn load(&self, session_id: &Id) -> session_store::Result> { + Ok(self + .0 + .lock() + .await + .get(session_id) + .filter(|Record { expiry_date, .. }| is_active(*expiry_date)) + .cloned()) + } + + async fn delete(&self, session_id: &Id) -> session_store::Result<()> { + self.0.lock().await.remove(session_id); + Ok(()) + } +} + +fn is_active(expiry_date: OffsetDateTime) -> bool { + expiry_date > OffsetDateTime::now_utc() +} + +#[cfg(test)] +mod tests { + use time::Duration; + + use super::*; + + #[tokio::test] + async fn test_create() { + let store = MemoryStore::default(); + let mut record = Record { + id: Default::default(), + data: Default::default(), + expiry_date: OffsetDateTime::now_utc() + Duration::minutes(30), + }; + assert!(store.create(&mut record).await.is_ok()); + } + + #[tokio::test] + async fn test_save() { + let store = MemoryStore::default(); + let record = Record { + id: Default::default(), + data: Default::default(), + expiry_date: OffsetDateTime::now_utc() + Duration::minutes(30), + }; + assert!(store.save(&record).await.is_ok()); + } + + #[tokio::test] + async fn test_load() { + let store = MemoryStore::default(); + let mut record = Record { + id: Default::default(), + data: Default::default(), + expiry_date: OffsetDateTime::now_utc() + Duration::minutes(30), + }; + store.create(&mut record).await.unwrap(); + let loaded_record = store.load(&record.id).await.unwrap(); + assert_eq!(Some(record), loaded_record); + } + + #[tokio::test] + async fn test_delete() { + let store = MemoryStore::default(); + let mut record = Record { + id: Default::default(), + data: Default::default(), + expiry_date: OffsetDateTime::now_utc() + Duration::minutes(30), + }; + store.create(&mut record).await.unwrap(); + assert!(store.delete(&record.id).await.is_ok()); + assert_eq!(None, store.load(&record.id).await.unwrap()); + } + + #[tokio::test] + async fn test_create_id_collision() { + let store = MemoryStore::default(); + let expiry_date = OffsetDateTime::now_utc() + Duration::minutes(30); + let mut record1 = Record { + id: Default::default(), + data: Default::default(), + expiry_date, + }; + let mut record2 = Record { + id: Default::default(), + data: Default::default(), + expiry_date, + }; + store.create(&mut record1).await.unwrap(); + record2.id = record1.id; // Set the same ID for record2 + store.create(&mut record2).await.unwrap(); + assert_ne!(record1.id, record2.id); // IDs should be different + } +} \ No newline at end of file diff --git a/src/session_storage/mod.rs b/src/session_storage/mod.rs new file mode 100644 index 0000000..d967859 --- /dev/null +++ b/src/session_storage/mod.rs @@ -0,0 +1,2 @@ +pub mod cloudflare; +pub mod in_memory; \ No newline at end of file diff --git a/src/utilities.rs b/src/utilities.rs new file mode 100644 index 0000000..c8a00c0 --- /dev/null +++ b/src/utilities.rs @@ -0,0 +1,30 @@ +pub struct Utilities; + +impl Utilities { + pub fn get_pkce_verifier_storage_key(csrf_string: &str) -> String { + format!("pkce_verifier_{}", csrf_string) + } + + pub fn get_auth_session_key(csrf_string: &str) -> String { + format!("csrf_session_{}", csrf_string) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_get_pkce_verifier_storage_key() { + let csrf_string = "test_csrf"; + let key = Utilities::get_pkce_verifier_storage_key(csrf_string); + assert_eq!(key, "pkce_verifier_test_csrf"); + } + + #[test] + fn test_get_auth_session_key() { + let csrf_string = "test_csrf"; + let key = Utilities::get_auth_session_key(csrf_string); + assert_eq!(key, "csrf_session_test_csrf"); + } +} diff --git a/src/zitadel_http.rs b/src/zitadel_http.rs new file mode 100644 index 0000000..4d4333c --- /dev/null +++ b/src/zitadel_http.rs @@ -0,0 +1,39 @@ +#[derive(Debug, serde::Deserialize)] +pub struct OidcMetadata { + pub issuer: String, + pub authorization_endpoint: String, + pub token_endpoint: String, + pub introspection_endpoint: Option, + pub userinfo_endpoint: Option, + pub revocation_endpoint: Option, + pub end_session_endpoint: Option, + pub device_authorization_endpoint: Option, + pub jwks_uri: String, + pub scopes_supported: Option>, + pub response_types_supported: Option>, + pub response_modes_supported: Option>, + pub grant_types_supported: Option>, + pub subject_types_supported: Option>, + pub id_token_signing_alg_values_supported: Option>, + pub request_object_signing_alg_values_supported: Option>, + pub token_endpoint_auth_methods_supported: Option>, + pub token_endpoint_auth_signing_alg_values_supported: Option>, + pub revocation_endpoint_auth_methods_supported: Option>, + pub revocation_endpoint_auth_signing_alg_values_supported: Option>, + pub introspection_endpoint_auth_methods_supported: Option>, + pub introspection_endpoint_auth_signing_alg_values_supported: Option>, + pub claims_supported: Option>, + pub code_challenge_methods_supported: Option>, + pub ui_locales_supported: Option>, + pub request_parameter_supported: Option, + pub request_uri_parameter_supported: Option, +} + +pub async fn fetch_oidc_metadata(issuer_url: &str) -> OidcMetadata { + let issuer_url = issuer_url.trim_end_matches('/'); + let metadata_url = format!("{}/.well-known/openid-configuration", issuer_url); + + let response = reqwest::get(&metadata_url).await.expect("Failed to fetch metadata"); + + response.json::().await.expect("Failed to parse metadata") +} diff --git a/temp_app b/temp_app new file mode 100644 index 0000000..d49fe22 --- /dev/null +++ b/temp_app @@ -0,0 +1,8 @@ + + { + "type": "application", + "keyId": "181963758610940161", + "key": "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAwT2YZJytkkZ1DDM3dcu1OA8YPzHu6XR8HotdMNRnV75GhOT4\nB7zDtdtoP8w/1NHHPEJ859e0kYhrrnKikOKLS6fS1KRsmqR5ZvTq8SlZ2mq3RcX2\nebZx5dQt36INij/WXdsBmjM/yfWvqqWBSb0L/186DaWwmmIxoXWe873vxRmlzblg\nGd8Nu07s9YTREbGPbtFVHEUM6xI4oIe8HJ0e1+JBkiGqk31Cogo0FoAxrOAg0Sf4\n5XiUMYIjzqh8673F9SC4IpVxG22mpFk3vDFuAITaStWYbiH2hPJNKWyX9HDCZb1D\nDqa3wZBDiLqWxh22hNZ6ZIe+3UoSGWsPBH+E1wIDAQABAoIBAD2v5QsRPRN57HmF\njAnNir8nimz6CrN53Pl/MbOZypenBSn9UfReXPeb3+6lzCarBPgGnYsBQAJJU16v\n95daym7PVy1Mg+Ll6F9mhe2Qbr+b23+pj2IRTNC6aB6Aw+PDNzJk7GEGRTG6fWZz\nSQ96Cu9tvcGHiBXwjLlnK+PRWU5IsCiLsjT4xBXsMLMw3YOdMK5z58sqr+SnNEyq\nRHoEvi9aC94WrargVB45Yx+81YNW8uQ5rMDmYaJC5a7ENz522SlAuf4T+fAGJ/HE\n/qbZGD4YwlLqAFDgewQ+5tEWEus3zgY2MIR7vN2zXU1Ptk+mQkXZl/Pxdp7q1xU+\nvr/kcykCgYEAy7MiIAzc1ctQDvkk3HiespzdQ/sC7+CGsBzkyubRc9Oq/YR7GfVK\nGTuDEDlWwx92VAvJGDWRa3T426YDyqiPj66uo836sgL15Uigg5afZun2bqGC78le\nBhSy9b+0YDHPa87GxtKt9UmMoB6WdmoPzOkLEEGS7eesmk2DDgY+QSUCgYEA8tr/\n3PawigL1cxuFpcO1lH6XUspGeAo5yB8FXvfW5g50e37LgooIvOFgUlYuchxwr6uh\nW+CUAWmm4farsgvMBMPYw+PbkCTi/xemiiDmMHUYd7sJkTl0JXApq3pZsNMg4Fw/\n29RynmcG8TGe2dkwrWp1aBYjvIHwEHuNHHTTA0sCgYBtSUFAwsXkaj0cm2y8YHZ8\nS46mv1AXFHYOnKHffjDXnLN7ao2FIsXLfdNWa/zxmLqqYtxUAcFwToSJi6szGnZT\nVxvZRFSBFveIOQvtLW1+EH4nYr3WGko4pvhQwrZqea7YH0skNrogBILPEToWc9bg\nUBOgeB31R7uh2X47kvvphQKBgQDWc60dYnniZVp5mwQZrQjbaC4YXaZ8ugrsPPhx\nNEoAPSN/KihrzZiJsjtsec3p1lNrzRNgHqCT3sgPIdPcFa7DRm5UDRIF54zL1gaq\nUwLyJ3TDxdZc928o4DLryc8J5mZRuSRq6t+MIU5wDnFHzhK+EBQ9Jc/I1rU22ONz\nDXaIoQKBgH14Apggo0o4Eo+OnEBRFbbDulaOfVLPTK9rktikbwO1vzDch8kdcwCU\nsvtRXHjDQL93Ih/8S9aDJZoSDulwr3VUsuDiDEb4jfYmP2sbNO4nIJt+SBMhVOXV\nt7E/uWK28X0GL/bIUzSMMgTfdjhXEtJW+s6hQU1fG+9U1qVTQ2R/\n-----END RSA PRIVATE KEY-----\n", + "appId": "181963751145079041", + "clientId": "181963751145144577@zitadel_rust_test" + } \ No newline at end of file diff --git a/temp_sa b/temp_sa new file mode 100644 index 0000000..d55b335 --- /dev/null +++ b/temp_sa @@ -0,0 +1,7 @@ + + { + "type": "serviceaccount", + "keyId": "181828078849229057", + "key": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpQIBAAKCAQEA9VIWALQqzx1ypi42t7MG4KSOMldD10brsEUjTcjqxhl6TJrP\nsjaNKWArnV/XH+6ZKRd55mUEFFx9VflqdwQtMVPjZKXpV4cFDiPwf1Z1h1DS6im4\nSo7eKR7OGb7TLBhwt7i2UPF4WnxBhTp/M6pG5kCJ1t8glIo5yRbrILXObRmvNWMz\nVIFAyw68NDZGYNhnR8AT43zjeJTFXG/suuEoXO/mMmMjsYY8kS0BbiQeq5t5hIrr\na/odswkDPn5Zd4P91iJHDnYlgfJuo3oRmgpOj/dDsl+vTol+vveeMO4TXPwZcl36\ngUNPok7nd6BA3gqmOS+fMImzmZB42trghARXXwIDAQABAoIBAQCbMOGQcml+ep+T\ntzqQPWYFaLQ37nKRVmE1Mpeh1o+G4Ik4utrXX6EvYpJUzVN29ObZUuufr5nEE7qK\nT+1k+zRntyzr9/VElLrC9kNnGtfg0WWMEvZt3DF4i+9P5CMNCy0LXIOhcxBzFZYR\nZS8hDQArGvrX/nFK5qKlrqTyHXFIHDFa6z59ErhXEnsTgRvx/Mo+6UkdBkHsKnlJ\nAbXqXFbfz6nDsF1DgRra5ODn1k8nZqnC/YcssE7/dlbuByz10ECkOSzqYcfufnsb\n9N1Ld4Xlj3yzsqPFzEJyHHm9eEHQXsPavaXiM64/+zpsksLscEIE/0KtIy5tngpZ\nSCqZAcj5AoGBAPb1bQFWUBmmUuSTtSymsxgXghJiJ3r+jJgdGbkv2IsRTs4En5Sz\n0SbPE1YWmMDDgTacJlB4/XiaojQ/j1EEY17inxYomE72UL6/ET7ycsEw3e9ALuD5\np0y2Sdzes2biH30bw5jD8kJ+hV18T745KtzrwSH4I0lAjnkmiH+0S67VAoGBAP5N\nTtAp/Qdxh9GjNSw1J7KRLtJrrr0pPrJ9av4GoFoWlz+Qw2X3dl8rjG3Bqz9LPV7A\ngiHMel8WTmdIM/S3F4Q3ufEfE+VzG+gncWd9SJfX5/LVhatPzTGLNsY7AYGEpSwT\n5/0anS1mHrLwsVcPrZnigekr5A5mfZl6nxtOnE9jAoGBALACqacbUkmFrmy1DZp+\nUQSptI3PoR3bEG9VxkCjZi1vr3/L8cS1CCslyT1BK6uva4d1cSVHpjfv1g1xA38V\nppE46XOMiUk16sSYPv1jJQCmCHd9givcIy3cefZOTwTTwueTAyv888wKipjfgaIs\n8my0JllEljmeJi0Ylo6V/J7lAoGBAIFqRlmZhLNtC3mcXUsKIhG14OYk9uA9RTMA\nsJpmNOSj6oTm3wndTdhRCT4x+TxUxf6aaZ9ZuEz7xRq6m/ZF1ynqUi5ramyyj9kt\neYD5OSBNODVUhJoSGpLEDjQDg1iucIBmAQHFsYeRGL5nz1hHGkneA87uDzlk3zZk\nOORktReRAoGAGUfU2UfaniAlqrZsSma3ZTlvJWs1x8cbVDyKTYMX5ShHhp+cA86H\nYjSSol6GI2wQPP+qIvZ1E8XyzD2miMJabl92/WY0tHejNNBEHwD8uBZKrtMoFWM7\nWJNl+Xneu/sT8s4pP2ng6QE7jpHXi2TUNmSlgQry9JN2AmA9TuSTW2Y=\n-----END RSA PRIVATE KEY-----\n", + "userId": "181828061098934529" + } \ No newline at end of file diff --git a/wrangler.jsonc b/wrangler.jsonc new file mode 100644 index 0000000..58cd867 --- /dev/null +++ b/wrangler.jsonc @@ -0,0 +1,27 @@ +{ + "$schema": "node_modules/wrangler/config-schema.json", + "compatibility_date": "2025-01-31", + "main": "build/worker/shim.mjs", + "name": "zitadel-session-worker", + "workers_dev": true, + "build": { + "command": "cargo install -q worker-build && worker-build --release" + }, + "services": [ + { + "binding": "PROXY_TARGET", + "service": "example-service" + } + ], + "kv_namespaces": [ + { + "binding": "KV_STORAGE", + "id": "your-id", + "preview_id": "your-preview-id" + } + ], + "dev": { + "port": 3000, + "ip": "localhost" + } +} \ No newline at end of file