diff --git a/Cargo.lock b/Cargo.lock index bae8e5b..ccbd794 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,11 +2,46 @@ # It is not intended for manual editing. version = 4 +[[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" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] @@ -49,9 +84,9 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "base16ct" @@ -73,9 +108,9 @@ checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" [[package]] name = "bitflags" -version = "2.9.1" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "block-buffer" @@ -88,36 +123,31 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.17.0" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "bytes" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" [[package]] name = "cc" -version = "1.2.25" +version = "1.2.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0fc897dc1e865cc67c0e05a836d9d3f1df3cbe442aa4a9473b18e12624a4951" +checksum = "b97463e1064cb1b1c1384ad0a0b9c8abd0988e2a91f52606c80ef14aadb63e36" dependencies = [ + "find-msvc-tools", "shlex", ] [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cfg_aliases" @@ -136,7 +166,17 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-link 0.2.0", + "windows-link", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", ] [[package]] @@ -145,7 +185,7 @@ version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -193,14 +233,24 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", + "rand_core 0.6.4", "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" @@ -241,9 +291,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.4.0" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" dependencies = [ "powerfmt", ] @@ -368,12 +418,12 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.12" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -398,6 +448,12 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" +[[package]] +name = "find-msvc-tools" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" + [[package]] name = "fnv" version = "1.0.7" @@ -469,9 +525,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.8" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dc8f7d2ded5f9209535e4b3fd4d39c002f30902ff5ce9f64e2c33d549576500" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", @@ -487,24 +543,34 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", "wasm-bindgen", ] [[package]] name = "getrandom" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "js-sys", "libc", "r-efi", - "wasi 0.14.2+wasi-0.2.4", + "wasip2", "wasm-bindgen", ] +[[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 = "group" version = "0.13.0" @@ -518,9 +584,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.10" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9421a676d1b147b16b82c9225157dc629087ef8ec4d5e2960f9437a90dac0a5" +checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" dependencies = [ "atomic-waker", "bytes", @@ -537,9 +603,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.3" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" [[package]] name = "hkdf" @@ -607,13 +673,14 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "1.6.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" dependencies = [ + "atomic-waker", "bytes", "futures-channel", - "futures-util", + "futures-core", "h2", "http", "http-body", @@ -621,6 +688,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", + "pin-utils", "smallvec", "tokio", "want", @@ -628,9 +696,9 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.6" +version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a01595e11bdcec50946522c32dde3fc6914743000a68b93000965f2f02406d" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ "http", "hyper", @@ -661,9 +729,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.13" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c293b6b3d21eca78250dc7dbebd6b9210ec5530e038cbfe0661b5c47ab06e8" +checksum = "52e9a2a24dc5c6821e71a7030e1e14b7b632acac55c40e9d2e082c621261bb56" dependencies = [ "base64", "bytes", @@ -677,7 +745,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.5.10", + "socket2", "system-configuration", "tokio", "tower-service", @@ -687,9 +755,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.63" +version = "0.1.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -711,9 +779,9 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" dependencies = [ "displaydoc", "potential_utf", @@ -724,9 +792,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" dependencies = [ "displaydoc", "litemap", @@ -737,11 +805,10 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" dependencies = [ - "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", @@ -752,42 +819,38 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" [[package]] name = "icu_properties" -version = "2.0.1" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99" dependencies = [ - "displaydoc", "icu_collections", "icu_locale_core", "icu_properties_data", "icu_provider", - "potential_utf", "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" -version = "2.0.1" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" +checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899" [[package]] name = "icu_provider" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" dependencies = [ "displaydoc", "icu_locale_core", - "stable_deref_trait", - "tinystr", "writeable", "yoke", "zerofrom", @@ -818,14 +881,23 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.9.0" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" dependencies = [ "equivalent", "hashbrown", ] +[[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" @@ -834,9 +906,9 @@ checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] name = "iri-string" -version = "0.7.8" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" +checksum = "4f867b9d1d896b67beb18518eda36fdb77a32ea590de864f1325b294a6d14397" dependencies = [ "memchr", "serde", @@ -850,9 +922,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "js-sys" -version = "0.3.77" +version = "0.3.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" dependencies = [ "once_cell", "wasm-bindgen", @@ -892,9 +964,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.172" +version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" [[package]] name = "libm" @@ -904,31 +976,30 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "linux-raw-sys" -version = "0.9.4" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "litemap" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" [[package]] name = "lock_api" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "autocfg", "scopeguard", ] [[package]] name = "log" -version = "0.4.27" +version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" [[package]] name = "lru-slab" @@ -944,9 +1015,9 @@ checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" [[package]] name = "memchr" -version = "2.7.4" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "mime" @@ -956,13 +1027,13 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "mio" -version = "1.0.4" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" dependencies = [ "libc", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.59.0", + "wasi", + "windows-sys 0.61.2", ] [[package]] @@ -981,7 +1052,7 @@ dependencies = [ "hyper", "hyper-util", "log", - "rand 0.9.1", + "rand 0.9.2", "regex", "serde_json", "serde_urlencoded", @@ -1018,11 +1089,10 @@ dependencies = [ [[package]] name = "num-bigint-dig" -version = "0.8.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" dependencies = [ - "byteorder", "lazy_static", "libm", "num-integer", @@ -1075,11 +1145,17 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + [[package]] name = "openssl" -version = "0.10.73" +version = "0.10.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" +checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" dependencies = [ "bitflags", "cfg-if", @@ -1109,9 +1185,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-sys" -version = "0.9.109" +version = "0.9.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" +checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" dependencies = [ "cc", "libc", @@ -1145,9 +1221,9 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", @@ -1155,25 +1231,25 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.11" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets 0.52.6", + "windows-link", ] [[package]] name = "pem" -version = "3.0.5" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38af38e8470ac9dee3ce1bae1af9c1671fffc44ddfd8bd1d0a3445bf349a8ef3" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" dependencies = [ "base64", - "serde", + "serde_core", ] [[package]] @@ -1230,11 +1306,23 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[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 = "potential_utf" -version = "0.1.2" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" dependencies = [ "zerovec", ] @@ -1265,9 +1353,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.95" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] @@ -1280,9 +1368,9 @@ checksum = "9318ead08c799aad12a55a3e78b82e0b6167271ffd1f627b758891282f739187" [[package]] name = "quinn" -version = "0.11.8" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "626214629cda6781b6dc1d316ba307189c85ba657213ce642d9c77670f8202c8" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" dependencies = [ "bytes", "cfg_aliases", @@ -1291,7 +1379,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls", - "socket2 0.5.10", + "socket2", "thiserror", "tokio", "tracing", @@ -1300,14 +1388,14 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.12" +version = "0.11.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e" +checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" dependencies = [ "bytes", - "getrandom 0.3.3", + "getrandom 0.3.4", "lru-slab", - "rand 0.9.1", + "rand 0.9.2", "ring", "rustc-hash", "rustls", @@ -1321,32 +1409,32 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.12" +version = "0.5.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee4e529991f949c5e25755532370b8af5d114acae52326361d68d47af64aa842" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.5.10", + "socket2", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.60.2", ] [[package]] name = "quote" -version = "1.0.40" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] [[package]] name = "r-efi" -version = "5.2.0" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rand" @@ -1361,9 +1449,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.3", @@ -1404,23 +1492,23 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", ] [[package]] name = "redox_syscall" -version = "0.5.12" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ "bitflags", ] [[package]] name = "regex" -version = "1.11.1" +version = "1.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" dependencies = [ "aho-corasick", "memchr", @@ -1430,9 +1518,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", @@ -1441,9 +1529,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.5" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "reqwest" @@ -1515,9 +1603,9 @@ dependencies = [ [[package]] name = "rsa" -version = "0.9.8" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b" +checksum = "40a0376c50d0358279d9d643e4bf7b7be212f1f4ff1da9070a7b54d22ef75c88" dependencies = [ "const-oid", "digest", @@ -1550,22 +1638,22 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.7" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] name = "rustls" -version = "0.23.27" +version = "0.23.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "730944ca083c1c233a75c09f199e973ca499344a2b7ba9e755c457e86fb4a321" +checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" dependencies = [ "once_cell", "ring", @@ -1577,9 +1665,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +checksum = "94182ad936a0c91c324cd46c6511b9510ed16af436d7b5bab34beab0afd55f7a" dependencies = [ "web-time", "zeroize", @@ -1587,9 +1675,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.3" +version = "0.103.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435" +checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" dependencies = [ "ring", "rustls-pki-types", @@ -1598,9 +1686,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" @@ -1610,11 +1698,11 @@ checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "schannel" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -1652,9 +1740,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.14.0" +version = "2.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" dependencies = [ "core-foundation-sys", "libc", @@ -1768,37 +1856,24 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.9" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" [[package]] name = "smallvec" -version = "1.15.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "socket2" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -1819,9 +1894,9 @@ dependencies = [ [[package]] name = "stable_deref_trait" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "subtle" @@ -1831,9 +1906,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.101" +version = "2.0.110" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea" dependencies = [ "proc-macro2", "quote", @@ -1883,15 +1958,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.20.0" +version = "3.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" dependencies = [ "fastrand", - "getrandom 0.3.3", + "getrandom 0.3.4", "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -1916,9 +1991,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.41" +version = "0.3.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" dependencies = [ "deranged", "itoa", @@ -1931,15 +2006,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.4" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" +checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" [[package]] name = "time-macros" -version = "0.2.22" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" dependencies = [ "num-conv", "time-core", @@ -1947,9 +2022,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" dependencies = [ "displaydoc", "zerovec", @@ -1957,9 +2032,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" dependencies = [ "tinyvec_macros", ] @@ -1981,9 +2056,9 @@ dependencies = [ "mio", "parking_lot", "pin-project-lite", - "socket2 0.6.0", + "socket2", "tokio-macros", - "windows-sys 0.61.1", + "windows-sys 0.61.2", ] [[package]] @@ -2009,9 +2084,9 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.26.2" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ "rustls", "tokio", @@ -2019,9 +2094,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.15" +version = "0.7.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" dependencies = [ "bytes", "futures-core", @@ -2087,9 +2162,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.33" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", ] @@ -2108,9 +2183,9 @@ checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "unicode-xid" @@ -2118,6 +2193,16 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[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" @@ -2171,50 +2256,37 @@ dependencies = [ [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] -name = "wasi" -version = "0.14.2+wasi-0.2.4" +name = "wasip2" +version = "1.0.1+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" dependencies = [ - "wit-bindgen-rt", + "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.100" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" 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" +version = "0.4.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +checksum = "551f88106c6d5e7ccc7cd9a16f312dd3b5d36ea8b4954304657d5dfba115d4a0" dependencies = [ "cfg-if", "js-sys", @@ -2225,9 +2297,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.100" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2235,31 +2307,31 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.100" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc" dependencies = [ + "bumpalo", "proc-macro2", "quote", "syn", - "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.100" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" -version = "0.3.77" +version = "0.3.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +checksum = "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1" dependencies = [ "js-sys", "wasm-bindgen", @@ -2277,31 +2349,31 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "1.0.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2853738d1cc4f2da3a225c18ec6c3721abb31961096e9dbf5ab35fa88b19cfdb" +checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e" dependencies = [ "rustls-pki-types", ] [[package]] name = "windows-core" -version = "0.61.2" +version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ "windows-implement", "windows-interface", - "windows-link 0.1.1", + "windows-link", "windows-result", - "windows-strings 0.4.2", + "windows-strings", ] [[package]] name = "windows-implement" -version = "0.60.0" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", @@ -2310,9 +2382,9 @@ dependencies = [ [[package]] name = "windows-interface" -version = "0.59.1" +version = "0.59.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", @@ -2321,52 +2393,37 @@ dependencies = [ [[package]] name = "windows-link" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" - -[[package]] -name = "windows-link" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-registry" -version = "0.4.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" dependencies = [ + "windows-link", "windows-result", - "windows-strings 0.3.1", - "windows-targets 0.53.0", + "windows-strings", ] [[package]] name = "windows-result" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" -dependencies = [ - "windows-link 0.1.1", -] - -[[package]] -name = "windows-strings" -version = "0.3.1" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ - "windows-link 0.1.1", + "windows-link", ] [[package]] name = "windows-strings" -version = "0.4.2" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ - "windows-link 0.1.1", + "windows-link", ] [[package]] @@ -2389,11 +2446,20 @@ dependencies = [ [[package]] name = "windows-sys" -version = "0.61.1" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f109e41dd4a3c848907eb83d5a42ea98b3769495597450cf6d153507b166f0f" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-link 0.2.0", + "windows-link", ] [[package]] @@ -2414,18 +2480,19 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.0" +version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", ] [[package]] @@ -2436,9 +2503,9 @@ checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[package]] name = "windows_aarch64_msvc" @@ -2448,9 +2515,9 @@ checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_aarch64_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" [[package]] name = "windows_i686_gnu" @@ -2460,9 +2527,9 @@ checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" [[package]] name = "windows_i686_gnullvm" @@ -2472,9 +2539,9 @@ checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[package]] name = "windows_i686_msvc" @@ -2484,9 +2551,9 @@ checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_i686_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[package]] name = "windows_x86_64_gnu" @@ -2496,9 +2563,9 @@ checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" [[package]] name = "windows_x86_64_gnullvm" @@ -2508,9 +2575,9 @@ checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" [[package]] name = "windows_x86_64_msvc" @@ -2520,24 +2587,24 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "windows_x86_64_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] -name = "wit-bindgen-rt" -version = "0.39.0" +name = "wit-bindgen" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" -dependencies = [ - "bitflags", -] +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "workos" version = "0.6.0" dependencies = [ + "aead", + "aes-gcm", "async-trait", + "base64", "chrono", "derive_more", "jsonwebtoken", @@ -2545,6 +2612,7 @@ dependencies = [ "mockito", "querystring", "reqwest", + "rsa", "serde", "serde_json", "thiserror", @@ -2555,17 +2623,16 @@ dependencies = [ [[package]] name = "writeable" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" [[package]] name = "yoke" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" dependencies = [ - "serde", "stable_deref_trait", "yoke-derive", "zerofrom", @@ -2573,9 +2640,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", @@ -2585,18 +2652,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.25" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.25" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", @@ -2626,15 +2693,15 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.8.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" [[package]] name = "zerotrie" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" dependencies = [ "displaydoc", "yoke", @@ -2643,9 +2710,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.2" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" dependencies = [ "yoke", "zerofrom", @@ -2654,9 +2721,9 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 5c8c3b4..f60e136 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,10 @@ native-tls = ["reqwest/native-tls"] rustls-tls = ["reqwest/rustls-tls"] [dependencies] +aead = { version = "0.5.2", features = ["std"] } +aes-gcm = "0.10.3" async-trait = "0.1.88" +base64 = "0.22.1" chrono = { version = "0.4.40", features = ["serde"] } derive_more = { version = "2.0.1", features = ["deref", "display", "from"] } jsonwebtoken = { version = "10.0.0", features = ["rust_crypto"] } @@ -28,6 +31,7 @@ urlencoding = "2.1.3" [dev-dependencies] matches = "0.1.10" mockito = "1.0.0" +rsa = "0.9.8" tokio = { version = "1.44.2", default-features = false, features = [ "macros", "rt-multi-thread", diff --git a/src/core/types.rs b/src/core/types.rs index a80910e..bce7262 100644 --- a/src/core/types.rs +++ b/src/core/types.rs @@ -2,6 +2,7 @@ mod api_key; mod metadata; mod paginated_list; mod pagination_params; +mod remote_jwk_set; mod timestamps; mod unpaginated_list; mod url_encodable_vec; @@ -10,6 +11,7 @@ pub use api_key::*; pub use metadata::*; pub use paginated_list::*; pub use pagination_params::*; +pub use remote_jwk_set::*; pub use timestamps::*; pub use unpaginated_list::*; pub(crate) use url_encodable_vec::*; diff --git a/src/core/types/remote_jwk_set.rs b/src/core/types/remote_jwk_set.rs new file mode 100644 index 0000000..915e953 --- /dev/null +++ b/src/core/types/remote_jwk_set.rs @@ -0,0 +1,84 @@ +use std::sync::{Arc, Mutex}; + +use chrono::{DateTime, FixedOffset, TimeDelta, Utc}; +use jsonwebtoken::jwk::{Jwk, JwkSet}; +use thiserror::Error; +use url::Url; + +use crate::{ResponseExt, WorkOsError, WorkOsResult, user_management::GetJwksError}; + +type Entry = (JwkSet, DateTime); + +/// An error returned from [`RemoteJwkSet::find`]. +#[derive(Debug, Error)] +pub enum FindJwkError { + /// Get JWKS error. + #[error(transparent)] + GetJwks(WorkOsError), + + /// Poison error. + #[error("poison error: {0}")] + Poison(String), +} + +impl From for WorkOsError { + fn from(value: FindJwkError) -> Self { + Self::Operation(value) + } +} + +/// Remote JSON Web Key Set (JWKS). +#[derive(Clone)] +pub struct RemoteJwkSet { + client: reqwest::Client, + url: Url, + jwks: Arc>>, +} + +impl RemoteJwkSet { + pub(crate) fn new(client: reqwest::Client, url: Url) -> Self { + RemoteJwkSet { + client, + url, + jwks: Arc::new(Mutex::new(None)), + } + } + + /// Find the key in the set that matches the given key id, if any. + pub async fn find(&self, kid: &str) -> WorkOsResult, FindJwkError> { + { + let jwks = self + .jwks + .lock() + .map_err(|err| FindJwkError::Poison(err.to_string()))?; + + if let Some((jwks, expires_at)) = jwks.as_ref() + && *expires_at > Utc::now().fixed_offset() + { + return Ok(jwks.find(kid).cloned()); + } + } + + let new_jwks = self + .client + .get(self.url.as_str()) + .send() + .await? + .handle_unauthorized_or_generic_error() + .await? + .json::() + .await?; + + let key = new_jwks.find(kid).cloned(); + + // TODO: Consider using the expiry of keys instead? + let mut jwks = self + .jwks + .lock() + .map_err(|err| FindJwkError::Poison(err.to_string()))?; + + *jwks = Some((new_jwks, Utc::now().fixed_offset() + TimeDelta::minutes(5))); + + Ok(key) + } +} diff --git a/src/sso/operations/get_authorization_url.rs b/src/sso/operations/get_authorization_url.rs index fe43f86..32770fc 100644 --- a/src/sso/operations/get_authorization_url.rs +++ b/src/sso/operations/get_authorization_url.rs @@ -105,10 +105,12 @@ impl GetAuthorizationUrl for Sso<'_> { ), }; + let redirect_uri = urlencoding::encode(redirect_uri); + let mut query_params: querystring::QueryParams = vec![ ("response_type", "code"), ("client_id", &client_id), - ("redirect_uri", redirect_uri), + ("redirect_uri", &redirect_uri), (connection_selector_param.0, &connection_selector_param.1), ]; @@ -149,7 +151,7 @@ mod test { assert_eq!( authorization_url, Url::parse( - "https://api.workos.com/sso/authorize?response_type=code&client_id=client_123456789&redirect_uri=https://your-app.com/callback&connection=conn_1234" + "https://api.workos.com/sso/authorize?response_type=code&client_id=client_123456789&redirect_uri=https%3A%2F%2Fyour-app.com%2Fcallback&connection=conn_1234" ) .unwrap() ) @@ -174,7 +176,7 @@ mod test { assert_eq!( authorization_url, Url::parse( - "https://api.workos.com/sso/authorize?response_type=code&client_id=client_123456789&redirect_uri=https://your-app.com/callback&organization=org_1234" + "https://api.workos.com/sso/authorize?response_type=code&client_id=client_123456789&redirect_uri=https%3A%2F%2Fyour-app.com%2Fcallback&organization=org_1234" ) .unwrap() ) @@ -197,7 +199,7 @@ mod test { assert_eq!( authorization_url, Url::parse( - "https://api.workos.com/sso/authorize?response_type=code&client_id=client_123456789&redirect_uri=https://your-app.com/callback&provider=GoogleOAuth" + "https://api.workos.com/sso/authorize?response_type=code&client_id=client_123456789&redirect_uri=https%3A%2F%2Fyour-app.com%2Fcallback&provider=GoogleOAuth" ) .unwrap() ) diff --git a/src/user_management.rs b/src/user_management.rs index 1975fee..7c9667e 100644 --- a/src/user_management.rs +++ b/src/user_management.rs @@ -2,24 +2,81 @@ //! //! [WorkOS Docs: User Management](https://workos.com/docs/user-management) +mod cookie_session; mod operations; mod types; +use std::sync::{Arc, Mutex}; + +pub use cookie_session::*; pub use operations::*; +use thiserror::Error; pub use types::*; -use crate::WorkOs; +use crate::{RemoteJwkSet, WorkOs}; + +/// An error returned from [`UserManagement::jwks`]. +#[derive(Debug, Error)] +pub enum JwksError { + /// Missing client ID + #[error("missing client ID")] + MissingClientId, + + /// Poison error. + #[error("poison error: {0}")] + Poison(String), + + /// URL error. + #[error(transparent)] + Url(#[from] url::ParseError), +} /// User Management. /// /// [WorkOS Docs: User Management](https://workos.com/docs/user-management) pub struct UserManagement<'a> { workos: &'a WorkOs, + jwks: Arc>>, } impl<'a> UserManagement<'a> { /// Returns a new [`UserManagement`] instance for the provided WorkOS client. pub fn new(workos: &'a WorkOs) -> Self { - Self { workos } + Self { + workos, + jwks: Arc::new(Mutex::new(None)), + } + } + + /// Get remote JSON Web Key Set (JWKS). + pub fn jwks(&'a self) -> Result { + let mut jwks = self + .jwks + .lock() + .map_err(|err| JwksError::Poison(err.to_string()))?; + + if let Some(jwks) = jwks.as_ref() { + return Ok(jwks.clone()); + } + + let Some(client_id) = self.workos.client_id() else { + return Err(JwksError::MissingClientId); + }; + + let new_jwks = + RemoteJwkSet::new(self.workos.client().clone(), self.get_jwks_url(client_id)?); + + *jwks = Some(new_jwks.clone()); + + Ok(new_jwks) + } + + /// Load the session by providing the sealed session and the cookie password. + pub fn load_sealed_session( + &'a self, + session_data: &'a str, + cookie_password: &'a str, + ) -> CookieSession<'a> { + CookieSession::new(self, session_data, cookie_password) } } diff --git a/src/user_management/cookie_session.rs b/src/user_management/cookie_session.rs new file mode 100644 index 0000000..5f59db5 --- /dev/null +++ b/src/user_management/cookie_session.rs @@ -0,0 +1,742 @@ +use aead::{AeadCore, OsRng}; +use aes_gcm::{Aes256Gcm, Key, KeyInit, Nonce, aead::Aead}; +use base64::{Engine, prelude::BASE64_STANDARD}; +use jsonwebtoken::{DecodingKey, Header, Validation, decode, decode_header}; +use thiserror::Error; +use url::{ParseError, Url}; + +use crate::{ + organizations::OrganizationId, + user_management::{ + AccessTokenClaims, AuthenticateWithRefreshToken, AuthenticateWithRefreshTokenParams, + AuthenticateWithSessionCookieError, AuthenticateWithSessionCookieResponse, GetLogoutUrl, + GetLogoutUrlParams, RefreshSessionError, RefreshSessionResponse, SessionCookieData, + UserManagement, + }, +}; + +/// The options for [`CookieSession::refresh`]. +#[derive(Debug, Default)] +pub struct RefreshOptions<'a> { + /// The password with which to encrypt the new session. + /// + /// Only pass this in if you want to set a new password separate from the one provided in [`UserManagement::load_sealed_session`]. + pub cookie_password: Option<&'a str>, + + /// The organization ID to use for the new session cookie. Use this if you want to switch organizations. + pub organization_id: Option<&'a OrganizationId>, +} + +/// The options for [`CookieSession::get_logout_url`]. +#[derive(Debug, Default)] +pub struct GetLogoutUrlOptions<'a> { + /// The location the user's browser should be redirected to by the WorkOS API after the session has been ended. + pub return_to: Option<&'a Url>, +} + +/// An error returned from [`CookieSession::get_logout_url`]. +#[derive(Debug, Error)] +pub enum GetLogoutUrlError { + /// Authenticate error. + #[error(transparent)] + Authenticate(#[from] AuthenticateWithSessionCookieError), + + /// URL error. + #[error(transparent)] + Url(#[from] ParseError), +} + +/// An error returned from [`CookieSession::seal_data`]. +#[derive(Debug, Error)] +pub enum SealDataError { + /// AES-GCM error. + #[error(transparent)] + AesGcm(#[from] aes_gcm::Error), + + /// JSON error. + #[error(transparent)] + Json(#[from] serde_json::Error), +} + +/// An error returned from [`CookieSession::unseal_data`]. +#[derive(Debug, Error)] +pub enum UnsealDataError { + /// Not enough data error. + #[error("not enough data")] + NotEnoughData, + + /// AES-GCM error. + #[error(transparent)] + AesGcm(#[from] aes_gcm::Error), + + /// Base64 decode error. + #[error(transparent)] + Base64(#[from] base64::DecodeError), + + /// JSON error. + #[error(transparent)] + Json(#[from] serde_json::Error), +} + +/// Cookie session. +pub struct CookieSession<'a> { + user_management: &'a UserManagement<'a>, + cookie_password: &'a str, + session_data: String, + + /// When provided, this is used instead of the JWKS. Should only be used in tests. + decoding_key: Option, +} + +impl<'a> CookieSession<'a> { + pub(crate) fn new( + user_management: &'a UserManagement<'a>, + session_data: &'a str, + cookie_password: &'a str, + ) -> Self { + Self { + user_management, + cookie_password, + session_data: session_data.to_string(), + decoding_key: None, + } + } + + /// Unseals the session data and checks if the session is still valid. + pub async fn authenticate( + &'a self, + ) -> Result { + let session = Self::unseal_data(&self.session_data, self.cookie_password)?; + + let Header { alg, kid, .. } = decode_header(&*session.access_token)?; + + let key = if let Some(decoding_key) = &self.decoding_key { + decoding_key.clone() + } else { + let kid = kid.ok_or(AuthenticateWithSessionCookieError::MissingJwkId)?; + + let jwks = self.user_management.jwks()?; + let jwk = jwks + .find(&kid) + .await? + .ok_or(AuthenticateWithSessionCookieError::JwkNotFound)?; + + DecodingKey::from_jwk(&jwk)? + }; + + let mut validation = Validation::new(alg); + validation.set_required_spec_claims(&Vec::::with_capacity(0)); + + let decoded = decode::(&*session.access_token, &key, &validation)?; + + Ok(AuthenticateWithSessionCookieResponse { + session_id: decoded.claims.sid.into(), + organization_id: decoded.claims.org_id.map(Into::into), + role: decoded.claims.role.map(Into::into), + permissions: decoded.claims.permissions, + entitlements: decoded.claims.entitlements, + feature_flags: decoded.claims.feature_flags, + user: session.user, + impersonator: session.impersonator, + access_token: session.access_token, + }) + } + + /// Refreshes the user’s session with the refresh token. + /// + /// Passing in a new organization ID will switch the user to that organization. + pub async fn refresh( + &mut self, + options: &RefreshOptions<'a>, + ) -> Result { + let session = Self::unseal_data(&self.session_data, self.cookie_password)?; + + let cookie_password = options.cookie_password.unwrap_or(self.cookie_password); + + let response = self + .user_management + .authenticate_with_refresh_token(&AuthenticateWithRefreshTokenParams { + client_id: self + .user_management + .workos + .client_id() + .ok_or(RefreshSessionError::MissingClientId)?, + refresh_token: &session.refresh_token, + organization_id: options.organization_id.or(session.organization_id.as_ref()), + ip_address: None, + user_agent: None, + }) + .await?; + let sealed_session = response.sealed_session(cookie_password)?; + + self.session_data = sealed_session.clone(); + self.cookie_password = cookie_password; + + Ok(RefreshSessionResponse { + sealed_session, + session: response, + }) + } + + /// Returns a logout URL the user's browser should be redirected to. + pub async fn get_logout_url( + &'a self, + options: &GetLogoutUrlOptions<'_>, + ) -> Result { + let authentication_response = self.authenticate().await?; + + Ok(self.user_management.get_logout_url(&GetLogoutUrlParams { + session_id: &authentication_response.session_id, + return_to: options.return_to, + })?) + } + + /// Encrypts and seals data using AES-256-GCM. + pub(crate) fn seal_data(data: SessionCookieData, key: &str) -> Result { + let iv = Aes256Gcm::generate_nonce(&mut OsRng); + let cipher = Aes256Gcm::new(&Self::key(key)); + + let decrypted_data = serde_json::to_string(&data)?; + + let encrypted_data = cipher.encrypt(&iv, decrypted_data.as_ref())?; + + let encoded_data = + BASE64_STANDARD.encode(iv.into_iter().chain(encrypted_data).collect::>()); + + Ok(encoded_data) + } + + /// Decrypts and unseals data using AES-256-GCM. + fn unseal_data(sealed_data: &str, key: &str) -> Result { + let decoded_data = BASE64_STANDARD.decode(sealed_data)?; + + if decoded_data.len() < 12 { + return Err(UnsealDataError::NotEnoughData); + } + + let iv = &decoded_data[0..12]; + let encrypted_data = &decoded_data[12..]; + + let cipher = Aes256Gcm::new(&Self::key(key)); + + let decrypted_data = cipher.decrypt(Nonce::from_slice(iv), encrypted_data)?; + + Ok(serde_json::from_slice(&decrypted_data)?) + } + + fn key(key: &str) -> Key { + let key = key.as_bytes(); + let length = key.len().min(32); + + let mut key_data = [0u8; 32]; + key_data[..length].copy_from_slice(&key[0..length]); + + Key::::from(key_data) + } +} + +#[cfg(test)] +mod tests { + use jsonwebtoken::DecodingKey; + use mockito::{Matcher, Server}; + use serde_json::json; + use url::Url; + + use crate::{ + ApiKey, Timestamps, WorkOs, + organizations::OrganizationId, + roles::RoleSlug, + sso::{AccessToken, ClientId}, + user_management::{ + AuthenticateWithSessionCookieError, AuthenticateWithSessionCookieResponse, + CookieSession, GetLogoutUrlError, GetLogoutUrlOptions, Impersonator, RefreshOptions, + RefreshSessionError, RefreshToken, SessionCookieData, SessionId, User, UserId, + }, + }; + + fn before() -> WorkOs { + WorkOs::builder(&ApiKey::from("sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU")) + .client_id(&ClientId::from("client_123")) + .build() + } + + fn before_with_base_url(base_url: &str) -> WorkOs { + WorkOs::builder(&ApiKey::from("sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU")) + .client_id(&ClientId::from("client_123")) + .base_url(base_url) + .unwrap() + .build() + } + + #[tokio::test] + async fn authenticate_returns_a_failed_response_if_no_access_token_is_found_in_the_session_data() + { + let workos = before(); + + let user_management = workos.user_management(); + let session = user_management.load_sealed_session("sessionData", "cookiePassword"); + + let response = session.authenticate().await; + + assert!(matches!( + response, + Err(AuthenticateWithSessionCookieError::InvalidSessionCookie(_)), + )); + } + + #[tokio::test] + async fn authenticate_returns_a_failed_response_if_the_access_token_is_not_a_valid_jwt() { + let workos = before(); + + let cookie_password = "alongcookiesecretmadefortestingsessions"; + + let session_data = CookieSession::seal_data( + SessionCookieData { + access_token: AccessToken::from("ewogICJzdWIiOiAiMTIzNDU2Nzg5MCIsCiAgIm5hbWUiOiAiSm9obiBEb2UiLAogICJpYXQiOiAxNTE2MjM5MDIyLAogICJzaWQiOiAic2Vzc2lvbl8xMjMiLAogICJvcmdfaWQiOiAib3JnXzEyMyIsCiAgInJvbGUiOiAibWVtYmVyIiwKICAicGVybWlzc2lvbnMiOiBbInBvc3RzOmNyZWF0ZSIsICJwb3N0czpkZWxldGUiXQp9"), + refresh_token: RefreshToken::from( "def456"), + user: User { + id: UserId::from("user_01H5JQDV7R7ATEYZDEG0W5PRYS"), + email: "test@example.com".to_string(), + email_verified: true, + first_name: None, + last_name: None, + profile_picture_url: None, + last_sign_in_at: None, + external_id: None, + metadata: None, + timestamps: Timestamps { + created_at: "2021-06-25T19:07:33.155Z".try_into().unwrap(), + updated_at: "2021-06-25T19:07:33.155Z".try_into().unwrap(), + }, + }, + organization_id: None, + impersonator: None, + }, + cookie_password, + ).unwrap(); + + let user_management = workos.user_management(); + let session = user_management.load_sealed_session(&session_data, cookie_password); + + let response = session.authenticate().await; + + assert!(matches!( + response, + Err(AuthenticateWithSessionCookieError::InvalidJwt(_)), + )); + } + + #[tokio::test] + async fn authenticate_returns_a_successful_response_if_the_session_data_is_valid() { + let workos = before(); + + let cookie_password = "alongcookiesecretmadefortestingsessions"; + let access_token = AccessToken::from( + "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdXRoZW50aWNhdGVkIjp0cnVlLCJpbXBlcnNvbmF0b3IiOnsiZW1haWwiOiJhZG1pbkBleGFtcGxlLmNvbSIsInJlYXNvbiI6InRlc3QifSwic2lkIjoic2Vzc2lvbl8xMjMiLCJvcmdfaWQiOiJvcmdfMTIzIiwicm9sZSI6Im1lbWJlciIsInJvbGVzIjpbIm1lbWJlciIsImFkbWluIl0sInBlcm1pc3Npb25zIjpbInBvc3RzOmNyZWF0ZSIsInBvc3RzOmRlbGV0ZSJdLCJlbnRpdGxlbWVudHMiOlsiYXVkaXQtbG9ncyJdLCJmZWF0dXJlX2ZsYWdzIjpbImRhcmstbW9kZSIsImJldGEtZmVhdHVyZXMiXSwidXNlciI6eyJvYmplY3QiOiJ1c2VyIiwiaWQiOiJ1c2VyXzAxSDVKUURWN1I3QVRFWVpERUcwVzVQUllTIiwiZW1haWwiOiJ0ZXN0QGV4YW1wbGUuY29tIn19.TNUzJYn6lzLWFFsiWiKEgIshyUs-bKJQf1VxwNr1cGI", + ); + let decoding_key = + DecodingKey::from_secret("a-string-secret-at-least-256-bits-long".as_bytes()); + + let session_data = CookieSession::seal_data( + SessionCookieData { + access_token: access_token.clone(), + refresh_token: RefreshToken::from("def456"), + impersonator: Some(Impersonator { + email: "admin@example.com".to_string(), + reason: Some("test".to_string()), + }), + user: User { + id: UserId::from("user_01H5JQDV7R7ATEYZDEG0W5PRYS"), + email: "test@example.com".to_string(), + email_verified: true, + first_name: None, + last_name: None, + profile_picture_url: None, + last_sign_in_at: None, + external_id: None, + metadata: None, + timestamps: Timestamps { + created_at: "2021-06-25T19:07:33.155Z".try_into().unwrap(), + updated_at: "2021-06-25T19:07:33.155Z".try_into().unwrap(), + }, + }, + organization_id: None, + }, + cookie_password, + ) + .unwrap(); + + let user_management = workos.user_management(); + let mut session = user_management.load_sealed_session(&session_data, cookie_password); + + // Use hardcoded decoding key instead of JWKS for testing. + session.decoding_key = Some(decoding_key); + + let response = session.authenticate().await.unwrap(); + + assert_eq!( + response, + AuthenticateWithSessionCookieResponse { + impersonator: Some(Impersonator { + email: "admin@example.com".to_string(), + reason: Some("test".to_string()), + }), + session_id: SessionId::from("session_123"), + organization_id: Some(OrganizationId::from("org_123")), + role: Some(RoleSlug::from("member")), + // roles: ["member", "admin"], + permissions: Some(vec!["posts:create".to_string(), "posts:delete".to_string()]), + entitlements: Some(vec!["audit-logs".to_string()]), + feature_flags: Some(vec!["dark-mode".to_string(), "beta-features".to_string()]), + user: User { + id: UserId::from("user_01H5JQDV7R7ATEYZDEG0W5PRYS"), + email: "test@example.com".to_string(), + email_verified: true, + first_name: None, + last_name: None, + profile_picture_url: None, + last_sign_in_at: None, + external_id: None, + metadata: None, + timestamps: Timestamps { + created_at: "2021-06-25T19:07:33.155Z".try_into().unwrap(), + updated_at: "2021-06-25T19:07:33.155Z".try_into().unwrap(), + }, + }, + access_token, + } + ) + } + + #[tokio::test] + async fn refresh_returns_a_failed_response_if_invalid_session_data_is_provided() { + let workos = before(); + + let user_management = workos.user_management(); + let mut session = user_management.load_sealed_session("", "cookiePassword"); + + let response = session.refresh(&RefreshOptions::default()).await; + + assert!(matches!( + response, + Err(RefreshSessionError::InvalidSessionCookie(_)) + )); + } + + #[tokio::test] + async fn refresh_returns_a_successful_response_if_the_session_data_is_valid() { + let mut server = Server::new_async().await; + + server + .mock("POST", "/user_management/authenticate") + .match_body(Matcher::PartialJson(json!({ + "client_id": "client_123", + "client_secret": "sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU", + "grant_type": "refresh_token", + "refresh_token": "def456", + }))) + .with_status(200) + .with_body( + json!({ + "user": { + "object": "user", + "id": "user_01H5JQDV7R7ATEYZDEG0W5PRYS", + "email": "test@example.com", + "first_name": null, + "last_name": null, + "email_verified": true, + "profile_picture_url": null, + "metadata": {}, + "created_at": "2021-06-25T19:07:33.155Z", + "updated_at": "2021-06-25T19:07:33.155Z" + }, + "organization_id": "org_123", + "access_token": "eyJhb.nNzb19vaWRjX2tleV9.lc5Uk4yWVk5In0", + "refresh_token": "yAjhKk123NLIjdrBdGZPf8pLIDvK", + "authentication_method": "SSO", + "impersonator": { + "email": "admin@example.com", + "reason": "Investigating an issue with the customer's account." + } + }) + .to_string(), + ) + .create_async() + .await; + + let workos = before_with_base_url(&server.url()); + + let cookie_password = "alongcookiesecretmadefortestingsessions"; + let access_token = AccessToken::from( + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJzaWQiOiJzZXNzaW9uXzEyMyIsIm9yZ19pZCI6Im9yZ18xMjMiLCJyb2xlIjoibWVtYmVyIiwicm9sZXMiOlsibWVtYmVyIiwiYWRtaW4iXSwicGVybWlzc2lvbnMiOlsicG9zdHM6Y3JlYXRlIiwicG9zdHM6ZGVsZXRlIl19.N5zveP149QhRR5zNvzGJPiCX098uXaN8VM1_lwsMg4A", + ); + let refresh_token = RefreshToken::from("def456"); + let decoding_key = + DecodingKey::from_secret("a-string-secret-at-least-256-bits-long".as_bytes()); + + let session_data = CookieSession::seal_data( + SessionCookieData { + access_token, + refresh_token, + impersonator: Some(Impersonator { + email: "admin@example.com".to_string(), + reason: Some("test".to_string()), + }), + user: User { + id: UserId::from("user_01H5JQDV7R7ATEYZDEG0W5PRYS"), + email: "test@example.com".to_string(), + email_verified: true, + first_name: None, + last_name: None, + profile_picture_url: None, + last_sign_in_at: None, + external_id: None, + metadata: None, + timestamps: Timestamps { + created_at: "2021-06-25T19:07:33.155Z".try_into().unwrap(), + updated_at: "2021-06-25T19:07:33.155Z".try_into().unwrap(), + }, + }, + organization_id: None, + }, + cookie_password, + ) + .unwrap(); + + let user_management = workos.user_management(); + let mut session = user_management.load_sealed_session(&session_data, cookie_password); + + // Use hardcoded decoding key instead of JWKS for testing. + session.decoding_key = Some(decoding_key); + + let response = session.refresh(&RefreshOptions::default()).await.unwrap(); + + assert_eq!(response.session.user.email, "test@example.com"); + } + + #[tokio::test] + async fn refresh_overwrites_the_cookie_password_if_a_new_one_is_provided() { + let access_token = AccessToken::from( + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJzaWQiOiJzZXNzaW9uXzEyMyIsIm9yZ19pZCI6Im9yZ18xMjMiLCJyb2xlIjoibWVtYmVyIiwicm9sZXMiOlsibWVtYmVyIiwiYWRtaW4iXSwicGVybWlzc2lvbnMiOlsicG9zdHM6Y3JlYXRlIiwicG9zdHM6ZGVsZXRlIl19.N5zveP149QhRR5zNvzGJPiCX098uXaN8VM1_lwsMg4A", + ); + let refresh_token = RefreshToken::from("def456"); + + let mut server = Server::new_async().await; + + server + .mock("POST", "/user_management/authenticate") + .match_body(Matcher::PartialJson(json!({ + "client_id": "client_123", + "client_secret": "sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU", + "grant_type": "refresh_token", + "refresh_token": "def456", + }))) + .with_status(200) + .with_body( + json!({ + "user": { + "object": "user", + "id": "user_01H5JQDV7R7ATEYZDEG0W5PRYS", + "email": "test@example.com", + "first_name": null, + "last_name": null, + "email_verified": true, + "profile_picture_url": null, + "metadata": {}, + "created_at": "2021-06-25T19:07:33.155Z", + "updated_at": "2021-06-25T19:07:33.155Z" + }, + "organization_id": "org_123", + "access_token": access_token, + "refresh_token": refresh_token, + "authentication_method": "SSO", + "impersonator": { + "email": "admin@example.com", + "reason": "Investigating an issue with the customer's account." + } + }) + .to_string(), + ) + .create_async() + .await; + + let workos = before_with_base_url(&server.url()); + + let cookie_password = "alongcookiesecretmadefortestingsessions"; + let decoding_key = + DecodingKey::from_secret("a-string-secret-at-least-256-bits-long".as_bytes()); + + let session_data = CookieSession::seal_data( + SessionCookieData { + access_token, + refresh_token, + impersonator: Some(Impersonator { + email: "admin@example.com".to_string(), + reason: Some("test".to_string()), + }), + user: User { + id: UserId::from("user_01H5JQDV7R7ATEYZDEG0W5PRYS"), + email: "test@example.com".to_string(), + email_verified: true, + first_name: None, + last_name: None, + profile_picture_url: None, + last_sign_in_at: None, + external_id: None, + metadata: None, + timestamps: Timestamps { + created_at: "2021-06-25T19:07:33.155Z".try_into().unwrap(), + updated_at: "2021-06-25T19:07:33.155Z".try_into().unwrap(), + }, + }, + organization_id: None, + }, + cookie_password, + ) + .unwrap(); + + let user_management = workos.user_management(); + let mut session = user_management.load_sealed_session(&session_data, cookie_password); + + // Use hardcoded decoding key instead of JWKS for testing. + session.decoding_key = Some(decoding_key); + + let new_cookie_password = "anevenlongercookiesecretmadefortestingsessions"; + + let response = session + .refresh(&RefreshOptions { + cookie_password: Some(new_cookie_password), + organization_id: None, + }) + .await; + + assert!(response.is_ok()); + + let response = session.authenticate().await; + + assert!(response.is_ok()); + } + + #[tokio::test] + async fn get_logout_url_returns_a_logout_url_for_the_user() { + let workos = before(); + + let cookie_password = "alongcookiesecretmadefortestingsessions"; + let decoding_key = + DecodingKey::from_secret("a-string-secret-at-least-256-bits-long".as_bytes()); + + let session_data = CookieSession::seal_data( + SessionCookieData { + access_token: AccessToken::from("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJzaWQiOiJzZXNzaW9uXzEyMyIsIm9yZ19pZCI6Im9yZ18xMjMiLCJyb2xlIjoibWVtYmVyIiwicGVybWlzc2lvbnMiOlsicG9zdHM6Y3JlYXRlIiwicG9zdHM6ZGVsZXRlIl19.3__E4RSr5WipYXEd5qcrstOcE263jXwxp3IuxG30rcM"), + refresh_token: RefreshToken::from("def456"), + impersonator: None, + user: User { + id: UserId::from("user_01H5JQDV7R7ATEYZDEG0W5PRYS"), + email: "test@example.com".to_string(), + email_verified: true, + first_name: None, + last_name: None, + profile_picture_url: None, + last_sign_in_at: None, + external_id: None, + metadata: None, + timestamps: Timestamps { + created_at: "2021-06-25T19:07:33.155Z".try_into().unwrap(), + updated_at: "2021-06-25T19:07:33.155Z".try_into().unwrap(), + }, + }, + organization_id: None, + }, + cookie_password, + ) + .unwrap(); + + let user_management = workos.user_management(); + let mut session = user_management.load_sealed_session(&session_data, cookie_password); + + // Use hardcoded decoding key instead of JWKS for testing. + session.decoding_key = Some(decoding_key); + + let url = session + .get_logout_url(&GetLogoutUrlOptions::default()) + .await + .unwrap(); + + assert_eq!( + url.to_string(), + "https://api.workos.com/user_management/sessions/logout?session_id=session_123" + ); + } + + #[tokio::test] + async fn get_logout_url_returns_an_error_if_the_session_is_invalid() { + let workos = before(); + + let user_management = workos.user_management(); + let session = user_management.load_sealed_session("", "cookiePassword"); + + let response = session + .get_logout_url(&GetLogoutUrlOptions::default()) + .await; + + assert!(matches!( + response, + Err(GetLogoutUrlError::Authenticate( + AuthenticateWithSessionCookieError::InvalidSessionCookie(_) + )), + )); + } + + #[tokio::test] + async fn get_logout_url_when_a_return_url_is_provided_returns_a_logout_url_for_the_user() { + let workos = before(); + + let cookie_password = "alongcookiesecretmadefortestingsessions"; + let decoding_key = + DecodingKey::from_secret("a-string-secret-at-least-256-bits-long".as_bytes()); + + let session_data = CookieSession::seal_data( + SessionCookieData { + access_token: AccessToken::from("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJzaWQiOiJzZXNzaW9uXzEyMyIsIm9yZ19pZCI6Im9yZ18xMjMiLCJyb2xlIjoibWVtYmVyIiwicGVybWlzc2lvbnMiOlsicG9zdHM6Y3JlYXRlIiwicG9zdHM6ZGVsZXRlIl19.3__E4RSr5WipYXEd5qcrstOcE263jXwxp3IuxG30rcM"), + refresh_token: RefreshToken::from("def456"), + impersonator: None, + user: User { + id: UserId::from("user_01H5JQDV7R7ATEYZDEG0W5PRYS"), + email: "test@example.com".to_string(), + email_verified: true, + first_name: None, + last_name: None, + profile_picture_url: None, + last_sign_in_at: None, + external_id: None, + metadata: None, + timestamps: Timestamps { + created_at: "2021-06-25T19:07:33.155Z".try_into().unwrap(), + updated_at: "2021-06-25T19:07:33.155Z".try_into().unwrap(), + }, + }, + organization_id: None, + }, + cookie_password, + ) + .unwrap(); + + let user_management = workos.user_management(); + let mut session = user_management.load_sealed_session(&session_data, cookie_password); + + // Use hardcoded decoding key instead of JWKS for testing. + session.decoding_key = Some(decoding_key); + + let url = session + .get_logout_url(&GetLogoutUrlOptions { + return_to: Some(&Url::parse("https://example.com/signed-out").unwrap()), + }) + .await + .unwrap(); + + assert_eq!( + url.to_string(), + "https://api.workos.com/user_management/sessions/logout?session_id=session_123&return_to=https%3A%2F%2Fexample.com%2Fsigned-out" + ); + } +} diff --git a/src/user_management/operations/get_authorization_url.rs b/src/user_management/operations/get_authorization_url.rs index 94b5db2..9f8bbe9 100644 --- a/src/user_management/operations/get_authorization_url.rs +++ b/src/user_management/operations/get_authorization_url.rs @@ -142,10 +142,12 @@ impl GetAuthorizationUrl for UserManagement<'_> { ), }; + let redirect_uri = urlencoding::encode(redirect_uri); + let mut query_params: querystring::QueryParams = vec![ ("response_type", "code"), ("client_id", &client_id), - ("redirect_uri", redirect_uri), + ("redirect_uri", &redirect_uri), (connection_selector_param.0, &connection_selector_param.1), ]; @@ -216,7 +218,7 @@ mod test { assert_eq!( authorization_url, Url::parse( - "https://api.workos.com/user_management/authorize?response_type=code&client_id=client_123456789&redirect_uri=https://your-app.com/callback&connection=conn_1234" + "https://api.workos.com/user_management/authorize?response_type=code&client_id=client_123456789&redirect_uri=https%3A%2F%2Fyour-app.com%2Fcallback&connection=conn_1234" ) .unwrap() ) @@ -244,7 +246,7 @@ mod test { assert_eq!( authorization_url, Url::parse( - "https://api.workos.com/user_management/authorize?response_type=code&client_id=client_123456789&redirect_uri=https://your-app.com/callback&organization=org_1234" + "https://api.workos.com/user_management/authorize?response_type=code&client_id=client_123456789&redirect_uri=https%3A%2F%2Fyour-app.com%2Fcallback&organization=org_1234" ) .unwrap() ) @@ -272,7 +274,7 @@ mod test { assert_eq!( authorization_url, Url::parse( - "https://api.workos.com/user_management/authorize?response_type=code&client_id=client_123456789&redirect_uri=https://your-app.com/callback&provider=GoogleOAuth" + "https://api.workos.com/user_management/authorize?response_type=code&client_id=client_123456789&redirect_uri=https%3A%2F%2Fyour-app.com%2Fcallback&provider=GoogleOAuth" ) .unwrap() ) @@ -300,7 +302,7 @@ mod test { assert_eq!( authorization_url, Url::parse( - "https://api.workos.com/user_management/authorize?response_type=code&client_id=client_123456789&redirect_uri=https://your-app.com/callback&provider=authkit&screen_hint=sign-in" + "https://api.workos.com/user_management/authorize?response_type=code&client_id=client_123456789&redirect_uri=https%3A%2F%2Fyour-app.com%2Fcallback&provider=authkit&screen_hint=sign-in" ) .unwrap() ) diff --git a/src/user_management/operations/get_logout_url.rs b/src/user_management/operations/get_logout_url.rs index 0bd6f85..f1929af 100644 --- a/src/user_management/operations/get_logout_url.rs +++ b/src/user_management/operations/get_logout_url.rs @@ -48,7 +48,7 @@ impl GetLogoutUrl for UserManagement<'_> { } = params; let session_id = session_id.to_string(); - let return_to = return_to.map(|return_to| return_to.to_string()); + let return_to = return_to.map(|return_to| urlencoding::encode(return_to.as_str())); let query = { let mut query_params: querystring::QueryParams = vec![("session_id", &session_id)]; @@ -92,7 +92,7 @@ mod test { assert_eq!( logout_url, - Url::parse("https://api.workos.com/user_management/sessions/logout?session_id=session_01HQAG1HENBZMAZD82YRXDFC0B&return_to=https://your-app.com/signed-out").unwrap() + Url::parse("https://api.workos.com/user_management/sessions/logout?session_id=session_01HQAG1HENBZMAZD82YRXDFC0B&return_to=https%3A%2F%2Fyour-app.com%2Fsigned-out").unwrap() ); Ok(()) diff --git a/src/user_management/types.rs b/src/user_management/types.rs index e610ac6..75e8815 100644 --- a/src/user_management/types.rs +++ b/src/user_management/types.rs @@ -1,5 +1,6 @@ mod authenticate_error; mod authenticate_methods; +mod authenticate_session_response; mod authentication_event; mod authentication_radar_risk_detected_event; mod authentication_response; @@ -14,12 +15,15 @@ mod password; mod password_reset; mod pending_authentication_token; mod provider; +mod refresh_session_response; mod refresh_token; mod session; +mod session_cookie_data; mod user; pub use authenticate_error::*; pub use authenticate_methods::*; +pub use authenticate_session_response::*; pub use authentication_event::*; pub use authentication_radar_risk_detected_event::*; pub use authentication_response::*; @@ -34,6 +38,8 @@ pub use password::*; pub use password_reset::*; pub use pending_authentication_token::*; pub use provider::*; +pub use refresh_session_response::*; pub use refresh_token::*; pub use session::*; +pub use session_cookie_data::*; pub use user::*; diff --git a/src/user_management/types/authenticate_session_response.rs b/src/user_management/types/authenticate_session_response.rs new file mode 100644 index 0000000..e9b3d5f --- /dev/null +++ b/src/user_management/types/authenticate_session_response.rs @@ -0,0 +1,68 @@ +use thiserror::Error; + +use crate::{ + FindJwkError, WorkOsError, + organizations::OrganizationId, + roles::RoleSlug, + sso::AccessToken, + user_management::{Impersonator, JwksError, SessionId, UnsealDataError, User}, +}; + +/// Authenticate with session cookie error. +#[derive(Debug, Error)] +pub enum AuthenticateWithSessionCookieError { + /// Invalid session cookie. + #[error("invalid session cookie: {0}")] + InvalidSessionCookie(#[from] UnsealDataError), + + /// Invalid JWT. + #[error("invalid JWT: {0}")] + InvalidJwt(#[from] jsonwebtoken::errors::Error), + + /// Missing JWK ID. + #[error("missing JWK ID")] + MissingJwkId, + + /// JWKS error. + #[error(transparent)] + Jwks(#[from] JwksError), + + /// Find JWK error. + #[error(transparent)] + FindJwk(#[from] WorkOsError), + + /// JWK not found in JWKS. + #[error("JWK not found in JWKS")] + JwkNotFound, +} + +/// Authenticate with session cookie response. +#[derive(Debug, PartialEq, Eq)] +pub struct AuthenticateWithSessionCookieResponse { + /// The ID of the session. + pub session_id: SessionId, + + /// The organization the user selected to sign in to. + pub organization_id: Option, + + /// The role of the user. + pub role: Option, + + /// A list of permission slugs. + pub permissions: Option>, + + /// A list of entitlements. + pub entitlements: Option>, + + /// A list of feature flags. + pub feature_flags: Option>, + + /// The user. + pub user: User, + + /// The WorkOS Dashboard user who is impersonating the user. + pub impersonator: Option, + + /// A JWT containing information about the session. + pub access_token: AccessToken, +} diff --git a/src/user_management/types/authentication_response.rs b/src/user_management/types/authentication_response.rs index 5e58d35..018986e 100644 --- a/src/user_management/types/authentication_response.rs +++ b/src/user_management/types/authentication_response.rs @@ -1,6 +1,10 @@ use serde::{Deserialize, Serialize}; -use crate::{organizations::OrganizationId, sso::AccessToken}; +use crate::{ + organizations::OrganizationId, + sso::AccessToken, + user_management::{CookieSession, SealDataError, SessionCookieData}, +}; use super::{Impersonator, RefreshToken, User}; @@ -36,7 +40,7 @@ pub enum AuthenticationMethod { } /// The response for authenticate requests. -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, PartialEq, Eq)] pub struct AuthenticationResponse { /// The corresponding user object. pub user: User, @@ -56,3 +60,19 @@ pub struct AuthenticationResponse { /// The WorkOS Dashboard user who is impersonating the user. pub impersonator: Option, } + +impl AuthenticationResponse { + /// The sealed session data. + pub fn sealed_session(&self, cookie_password: &str) -> Result { + CookieSession::seal_data( + SessionCookieData { + user: self.user.clone(), + organization_id: self.organization_id.clone(), + access_token: self.access_token.clone(), + refresh_token: self.refresh_token.clone(), + impersonator: self.impersonator.clone(), + }, + cookie_password, + ) + } +} diff --git a/src/user_management/types/impersonator.rs b/src/user_management/types/impersonator.rs index 0bc804e..e327189 100644 --- a/src/user_management/types/impersonator.rs +++ b/src/user_management/types/impersonator.rs @@ -1,7 +1,7 @@ use serde::{Deserialize, Serialize}; /// [WorkOS Docs: Impersonation](https://workos.com/docs/user-management/impersonation) -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct Impersonator { /// The email address of the WorkOS Dashboard user who is impersonating the user pub email: String, diff --git a/src/user_management/types/refresh_session_response.rs b/src/user_management/types/refresh_session_response.rs new file mode 100644 index 0000000..ddf6d12 --- /dev/null +++ b/src/user_management/types/refresh_session_response.rs @@ -0,0 +1,36 @@ +use thiserror::Error; + +use crate::{ + WorkOsError, + user_management::{AuthenticateError, AuthenticationResponse, SealDataError, UnsealDataError}, +}; + +/// Refresh session error. +#[derive(Debug, Error)] +pub enum RefreshSessionError { + /// Invalid session cookie. + #[error("invalid session cookie: {0}")] + InvalidSessionCookie(#[from] UnsealDataError), + + /// Missing client ID. + #[error("missing client ID")] + MissingClientId, + + /// Authenticate error. + #[error(transparent)] + Authenticate(#[from] WorkOsError), + + /// Seal data error. + #[error(transparent)] + SealData(#[from] SealDataError), +} + +/// Refresh session response. +#[derive(Debug, PartialEq, Eq)] +pub struct RefreshSessionResponse { + /// The sealed session. + pub sealed_session: String, + + /// The session. + pub session: AuthenticationResponse, +} diff --git a/src/user_management/types/session_cookie_data.rs b/src/user_management/types/session_cookie_data.rs new file mode 100644 index 0000000..811e779 --- /dev/null +++ b/src/user_management/types/session_cookie_data.rs @@ -0,0 +1,48 @@ +use serde::{Deserialize, Serialize}; + +use crate::{ + organizations::OrganizationId, + sso::AccessToken, + user_management::{Impersonator, RefreshToken, User}, +}; + +/// The claims in an access token. +#[derive(Debug, Deserialize)] +pub struct AccessTokenClaims { + /// The ID of the session. + pub sid: String, + + /// The organization the user selected to sign in to. + pub org_id: Option, + + /// The role of the user. + pub role: Option, + + /// A list of permissions. + pub permissions: Option>, + + /// A list of entitlements. + pub entitlements: Option>, + + /// A list of feature flags. + pub feature_flags: Option>, +} + +/// The data in a session cookie. +#[derive(Debug, Serialize, Deserialize)] +pub struct SessionCookieData { + /// The corresponding user object. + pub user: User, + + /// The organization the user selected to sign in to. + pub organization_id: Option, + + /// A JWT containing information about the session. + pub access_token: AccessToken, + + /// Exchange this token for a new access token. + pub refresh_token: RefreshToken, + + /// The WorkOS Dashboard user who is impersonating the user. + pub impersonator: Option, +} diff --git a/src/workos.rs b/src/workos.rs index 9e4980c..4c5cd05 100644 --- a/src/workos.rs +++ b/src/workos.rs @@ -8,7 +8,7 @@ use crate::organization_domains::OrganizationDomains; use crate::organizations::Organizations; use crate::portal::Portal; use crate::roles::Roles; -use crate::sso::Sso; +use crate::sso::{ClientId, Sso}; use crate::user_management::UserManagement; use crate::widgets::Widgets; @@ -18,6 +18,7 @@ pub struct WorkOs { base_url: Url, key: ApiKey, client: reqwest::Client, + client_id: Option, } impl WorkOs { @@ -43,6 +44,10 @@ impl WorkOs { &self.client } + pub(crate) fn client_id(&self) -> Option<&ClientId> { + self.client_id.as_ref() + } + /// Returns a [`DirectorySync`] instance. pub fn directory_sync(&self) -> DirectorySync<'_> { DirectorySync::new(self) @@ -98,6 +103,7 @@ impl WorkOs { pub struct WorkOsBuilder<'a> { base_url: Url, key: &'a ApiKey, + client_id: Option<&'a ClientId>, } impl<'a> WorkOsBuilder<'a> { @@ -106,6 +112,7 @@ impl<'a> WorkOsBuilder<'a> { Self { base_url: Url::parse("https://api.workos.com").unwrap(), key, + client_id: None, } } @@ -121,6 +128,12 @@ impl<'a> WorkOsBuilder<'a> { self } + /// Sets the client ID that the client will use. + pub fn client_id(mut self, client_id: &'a ClientId) -> Self { + self.client_id = Some(client_id); + self + } + /// Consumes the builder and returns the constructed client. pub fn build(self) -> WorkOs { let client = reqwest::Client::builder() @@ -132,6 +145,7 @@ impl<'a> WorkOsBuilder<'a> { base_url: self.base_url, key: self.key.to_owned(), client, + client_id: self.client_id.cloned(), } } }