diff --git a/Cargo.lock b/Cargo.lock index f8609d1..c40b6a2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -52,6 +52,7 @@ dependencies = [ "tokio", "tower", "tower-http", + "tower-sessions", "tower-sessions-sqlx-store", "tracing", "tracing-subscriber", @@ -83,7 +84,7 @@ version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86dde77d8a733a9dbaf865a9eb65c72e09c88f3d14d3dd0d2aecf511920ee4fe" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "futures-util", "memchr", @@ -92,7 +93,7 @@ dependencies = [ "once_cell", "pin-project", "portable-atomic", - "rand", + "rand 0.8.5", "regex", "ring", "rustls-native-certs", @@ -229,6 +230,18 @@ dependencies = [ "syn", ] +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + [[package]] name = "base64" version = "0.22.1" @@ -305,6 +318,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "chrono" version = "0.4.42" @@ -455,6 +474,18 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.7" @@ -478,6 +509,7 @@ dependencies = [ "fiat-crypto", "rustc_version", "subtle", + "zeroize", ] [[package]] @@ -497,8 +529,18 @@ version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.20.11", + "darling_macro 0.20.11", +] + +[[package]] +name = "darling" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +dependencies = [ + "darling_core 0.21.3", + "darling_macro 0.21.3", ] [[package]] @@ -515,13 +557,38 @@ dependencies = [ "syn", ] +[[package]] +name = "darling_core" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + [[package]] name = "darling_macro" version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ - "darling_core", + "darling_core 0.20.11", + "quote", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +dependencies = [ + "darling_core 0.21.3", "quote", "syn", ] @@ -607,12 +674,33 @@ version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + [[package]] name = "ed25519" version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" dependencies = [ + "pkcs8", "signature", ] @@ -624,9 +712,11 @@ checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" dependencies = [ "curve25519-dalek", "ed25519", + "serde", "sha2", "signature", "subtle", + "zeroize", ] [[package]] @@ -638,6 +728,27 @@ dependencies = [ "serde", ] +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "hkdf", + "pem-rfc7468", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "encoding_rs" version = "0.8.35" @@ -696,6 +807,16 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "fiat-crypto" version = "0.2.9" @@ -846,6 +967,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -868,11 +990,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi", "wasip2", + "wasm-bindgen", ] +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "hashbrown" version = "0.14.5" @@ -1008,6 +1149,24 @@ dependencies = [ "pin-utils", "smallvec", "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots 1.0.5", ] [[package]] @@ -1016,14 +1175,22 @@ version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" dependencies = [ + "base64 0.22.1", "bytes", + "futures-channel", "futures-core", + "futures-util", "http", "http-body", "hyper", + "ipnet", + "libc", + "percent-encoding", "pin-project-lite", + "socket2", "tokio", "tower-service", + "tracing", ] [[package]] @@ -1158,6 +1325,17 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + [[package]] name = "indexmap" version = "2.12.1" @@ -1166,6 +1344,8 @@ checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" dependencies = [ "equivalent", "hashbrown 0.16.1", + "serde", + "serde_core", ] [[package]] @@ -1181,6 +1361,7 @@ dependencies = [ "futures-core", "futures-util", "k-core", + "openidconnect", "password-auth", "serde", "serde_json", @@ -1190,9 +1371,35 @@ dependencies = [ "tower-sessions", "tower-sessions-sqlx-store", "tracing", + "url", "uuid", ] +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "iri-string" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.17" @@ -1311,6 +1518,12 @@ version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + [[package]] name = "matchers" version = "0.2.0" @@ -1370,7 +1583,7 @@ dependencies = [ "ed25519-dalek", "getrandom 0.2.16", "log", - "rand", + "rand 0.8.5", "signatory", ] @@ -1389,7 +1602,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc895af95856f929163a0aa20c26a78d26bfdc839f51b9d5aa7a5b79e52b7e83" dependencies = [ - "rand", + "rand 0.8.5", ] [[package]] @@ -1403,7 +1616,7 @@ dependencies = [ "num-integer", "num-iter", "num-traits", - "rand", + "rand 0.8.5", "smallvec", "zeroize", ] @@ -1444,18 +1657,78 @@ dependencies = [ "libm", ] +[[package]] +name = "oauth2" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51e219e79014df21a225b1860a479e2dcd7cbd9130f4defd4bd0e191ea31d67d" +dependencies = [ + "base64 0.22.1", + "chrono", + "getrandom 0.2.16", + "http", + "rand 0.8.5", + "reqwest", + "serde", + "serde_json", + "serde_path_to_error", + "sha2", + "thiserror 1.0.69", + "url", +] + [[package]] name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "openidconnect" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c6709ba2ea764bbed26bce1adf3c10517113ddea6f2d4196e4851757ef2b2" +dependencies = [ + "base64 0.21.7", + "chrono", + "dyn-clone", + "ed25519-dalek", + "hmac", + "http", + "itertools", + "log", + "oauth2", + "p256", + "p384", + "rand 0.8.5", + "rsa", + "serde", + "serde-value", + "serde_json", + "serde_path_to_error", + "serde_plain", + "serde_with", + "sha2", + "subtle", + "thiserror 1.0.69", + "url", +] + [[package]] name = "openssl-probe" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" +[[package]] +name = "ordered-float" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" +dependencies = [ + "num-traits", +] + [[package]] name = "ordered-multimap" version = "0.7.3" @@ -1466,6 +1739,30 @@ dependencies = [ "hashbrown 0.14.5", ] +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "p384" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + [[package]] name = "parking" version = "2.2.1" @@ -1504,7 +1801,7 @@ dependencies = [ "argon2", "getrandom 0.2.16", "password-hash", - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -1514,7 +1811,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" dependencies = [ "base64ct", - "rand_core", + "rand_core 0.6.4", "subtle", ] @@ -1671,6 +1968,15 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "proc-macro-error-attr2" version = "2.0.0" @@ -1702,6 +2008,61 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.17", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" +dependencies = [ + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand 0.9.2", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.17", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.60.2", +] + [[package]] name = "quote" version = "1.0.42" @@ -1724,8 +2085,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", ] [[package]] @@ -1735,7 +2106,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", ] [[package]] @@ -1747,6 +2128,15 @@ dependencies = [ "getrandom 0.2.16", ] +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.4", +] + [[package]] name = "redox_syscall" version = "0.5.18" @@ -1765,6 +2155,26 @@ dependencies = [ "bitflags", ] +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "regex" version = "1.12.2" @@ -1794,6 +2204,54 @@ version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots 1.0.5", +] + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "ring" version = "0.17.14" @@ -1854,7 +2312,7 @@ dependencies = [ "num-traits", "pkcs1", "pkcs8", - "rand_core", + "rand_core 0.6.4", "signature", "spki", "subtle", @@ -1871,6 +2329,12 @@ dependencies = [ "ordered-multimap", ] +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + [[package]] name = "rustc_version" version = "0.4.1" @@ -1922,6 +2386,7 @@ version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282" dependencies = [ + "web-time", "zeroize", ] @@ -1967,12 +2432,50 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54e910108742c57a770f492731f99be216a52fadd361b06c8fb59d74ccc267d2" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + [[package]] name = "security-framework" version = "2.11.1" @@ -2024,6 +2527,16 @@ dependencies = [ "typeid", ] +[[package]] +name = "serde-value" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" +dependencies = [ + "ordered-float", + "serde", +] + [[package]] name = "serde_core" version = "1.0.228" @@ -2077,6 +2590,15 @@ dependencies = [ "serde_core", ] +[[package]] +name = "serde_plain" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1fc6db65a611022b23a0dec6975d63fb80a302cb3388835ff02c097258d50" +dependencies = [ + "serde", +] + [[package]] name = "serde_repr" version = "0.1.20" @@ -2109,6 +2631,37 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "3.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" +dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.12.1", + "schemars 0.9.0", + "schemars 1.2.0", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" +dependencies = [ + "darling 0.21.3", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "sha1" version = "0.10.6" @@ -2163,7 +2716,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1e303f8205714074f6068773f0e29527e0453937fe837c9717d066635b65f31" dependencies = [ "pkcs8", - "rand_core", + "rand_core 0.6.4", "signature", "zeroize", ] @@ -2175,7 +2728,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest", - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -2241,7 +2794,7 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "chrono", "crc", @@ -2254,7 +2807,7 @@ dependencies = [ "futures-util", "hashbrown 0.15.5", "hashlink", - "indexmap", + "indexmap 2.12.1", "log", "memchr", "once_cell", @@ -2317,7 +2870,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" dependencies = [ "atoi", - "base64", + "base64 0.22.1", "bitflags", "byteorder", "bytes", @@ -2340,7 +2893,7 @@ dependencies = [ "memchr", "once_cell", "percent-encoding", - "rand", + "rand 0.8.5", "rsa", "serde", "sha1", @@ -2362,7 +2915,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" dependencies = [ "atoi", - "base64", + "base64 0.22.1", "bitflags", "byteorder", "chrono", @@ -2381,7 +2934,7 @@ dependencies = [ "md-5", "memchr", "once_cell", - "rand", + "rand 0.8.5", "serde", "serde_json", "sha2", @@ -2467,6 +3020,9 @@ name = "sync_wrapper" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] [[package]] name = "synstructure" @@ -2661,13 +3217,13 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f591660438b3038dd04d16c938271c79e7e06260ad2ea2885a4861bfb238605d" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "futures-core", "futures-sink", "http", "httparse", - "rand", + "rand 0.8.5", "ring", "rustls-pki-types", "tokio", @@ -2747,9 +3303,12 @@ checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ "bitflags", "bytes", + "futures-util", "http", "http-body", + "iri-string", "pin-project-lite", + "tower", "tower-layer", "tower-service", "tracing", @@ -2793,11 +3352,11 @@ checksum = "ce8cce604865576b7751b7a6bc3058f754569a60d689328bb74c52b1d87e355b" dependencies = [ "async-trait", "axum-core", - "base64", + "base64 0.22.1", "futures", "http", "parking_lot", - "rand", + "rand 0.8.5", "serde", "serde_json", "thiserror 2.0.17", @@ -2894,6 +3453,12 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "tryhard" version = "0.5.2" @@ -2963,14 +3528,15 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.7" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ "form_urlencoded", "idna", "percent-encoding", "serde", + "serde_derive", ] [[package]] @@ -3019,7 +3585,7 @@ version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7df16e474ef958526d1205f6dda359fdfab79d9aa6d54bafcb92dcd07673dca" dependencies = [ - "darling", + "darling 0.20.11", "once_cell", "proc-macro-error2", "proc-macro2", @@ -3045,6 +3611,15 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -3079,6 +3654,19 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.106" @@ -3111,6 +3699,26 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "web-sys" +version = "0.3.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "webpki-roots" version = "0.26.11" diff --git a/api/Cargo.toml b/api/Cargo.toml index d01bd69..f0a7290 100644 --- a/api/Cargo.toml +++ b/api/Cargo.toml @@ -5,10 +5,11 @@ edition = "2024" default-run = "api" [features] -default = ["sqlite", "auth-axum-login"] +default = ["sqlite", "auth-axum-login", "auth-oidc"] sqlite = ["infra/sqlite", "tower-sessions-sqlx-store/sqlite"] postgres = ["infra/postgres", "tower-sessions-sqlx-store/postgres"] auth-axum-login = ["infra/auth-axum-login"] +auth-oidc = ["infra/auth-oidc"] [dependencies] k-core = { git = "https://git.gabrielkaszewski.dev/GKaszewski/k-core", features = [ @@ -58,3 +59,4 @@ tracing-subscriber = { version = "0.3.22", features = ["env-filter"] } dotenvy = "0.15.7" config = "0.15.19" +tower-sessions = "0.14.0" diff --git a/api/src/config.rs b/api/src/config.rs index 156f473..defa550 100644 --- a/api/src/config.rs +++ b/api/src/config.rs @@ -6,6 +6,7 @@ use std::env; use serde::Deserialize; +//todo: replace with newtypes #[derive(Debug, Clone, Deserialize)] pub struct Config { pub database_url: String, @@ -26,6 +27,11 @@ pub struct Config { #[serde(default = "default_db_min_connections")] pub db_min_connections: u32, + + pub oidc_issuer: Option, + pub oidc_client_id: Option, + pub oidc_client_secret: Option, + pub oidc_redirect_url: Option, } fn default_secure_cookie() -> bool { @@ -98,6 +104,11 @@ impl Config { .and_then(|s| s.parse().ok()) .unwrap_or(1); + let oidc_issuer = env::var("OIDC_ISSUER").ok(); + let oidc_client_id = env::var("OIDC_CLIENT_ID").ok(); + let oidc_client_secret = env::var("OIDC_CLIENT_SECRET").ok(); + let oidc_redirect_url = env::var("OIDC_REDIRECT_URL").ok(); + Self { host, port, @@ -107,6 +118,10 @@ impl Config { secure_cookie, db_max_connections, db_min_connections, + oidc_issuer, + oidc_client_id, + oidc_client_secret, + oidc_redirect_url, } } } diff --git a/api/src/main.rs b/api/src/main.rs index 9ffad01..287e59a 100644 --- a/api/src/main.rs +++ b/api/src/main.rs @@ -49,7 +49,7 @@ async fn main() -> anyhow::Result<()> { let user_repo = build_user_repository(&db_pool).await?; let user_service = UserService::new(user_repo.clone()); - let state = AppState::new(user_service, config.clone()); + let state = AppState::new(user_service, config.clone()).await?; let session_store = build_session_store(&db_pool) .await diff --git a/api/src/routes/auth.rs b/api/src/routes/auth.rs index a506e3b..8124c72 100644 --- a/api/src/routes/auth.rs +++ b/api/src/routes/auth.rs @@ -12,13 +12,21 @@ use crate::{ state::AppState, }; use domain::{DomainError, Email}; +use tower_sessions::Session; pub fn router() -> Router { - Router::new() + let r = Router::new() .route("/login", post(login)) .route("/register", post(register)) .route("/logout", post(logout)) - .route("/me", post(me)) + .route("/me", post(me)); + + #[cfg(feature = "auth-oidc")] + let r = r + .route("/login/oidc", axum::routing::get(oidc_login)) + .route("/auth/callback", axum::routing::get(oidc_callback)); + + r } async fn login( @@ -115,3 +123,104 @@ async fn me(auth_session: crate::auth::AuthSession) -> Result, + session: Session, +) -> Result { + let service = state + .oidc_service + .as_ref() + .ok_or(ApiError::Internal("OIDC not configured".into()))?; + + let (url, csrf, nonce, pkce) = service.get_authorization_url(); + + session + .insert("oidc_csrf", csrf) + .await + .map_err(|_| ApiError::Internal("Session error".into()))?; + session + .insert("oidc_nonce", nonce) + .await + .map_err(|_| ApiError::Internal("Session error".into()))?; + session + .insert("oidc_pkce", pkce) + .await + .map_err(|_| ApiError::Internal("Session error".into()))?; + + Ok(axum::response::Redirect::to(&url)) +} + +#[cfg(feature = "auth-oidc")] +#[derive(serde::Deserialize)] +struct CallbackParams { + code: String, + state: String, +} + +#[cfg(feature = "auth-oidc")] +async fn oidc_callback( + State(state): State, + session: Session, + mut auth_session: crate::auth::AuthSession, + axum::extract::Query(params): axum::extract::Query, +) -> Result { + let service = state + .oidc_service + .as_ref() + .ok_or(ApiError::Internal("OIDC not configured".into()))?; + + let stored_csrf: String = session + .get("oidc_csrf") + .await + .map_err(|_| ApiError::Internal("Session error".into()))? + .ok_or(ApiError::Validation("Missing CSRF token".into()))?; + + if params.state != stored_csrf { + return Err(ApiError::Validation("Invalid CSRF token".into())); + } + + // 2. Retrieve secrets + let stored_pkce: String = session + .get("oidc_pkce") + .await + .map_err(|_| ApiError::Internal("Session error".into()))? + .ok_or(ApiError::Validation("Missing PKCE".into()))?; + let stored_nonce: String = session + .get("oidc_nonce") + .await + .map_err(|_| ApiError::Internal("Session error".into()))? + .ok_or(ApiError::Validation("Missing Nonce".into()))?; + + let oidc_user = service + .resolve_callback(params.code, stored_nonce, stored_pkce) + .await + .map_err(|e| ApiError::Internal(e.to_string()))?; + + let user = state + .user_service + .find_or_create(&oidc_user.subject, &oidc_user.email) + .await + .map_err(|e| ApiError::Internal(e.to_string()))?; + + auth_session + .login(&crate::auth::AuthUser(user)) + .await + .map_err(|_| ApiError::Internal("Login failed".into()))?; + + let _: Option = session + .remove("oidc_csrf") + .await + .map_err(|_| ApiError::Internal("Session error".into()))?; + let _: Option = session + .remove("oidc_pkce") + .await + .map_err(|_| ApiError::Internal("Session error".into()))?; + let _: Option = session + .remove("oidc_nonce") + .await + .map_err(|_| ApiError::Internal("Session error".into()))?; + + Ok(axum::response::Redirect::to("/")) +} diff --git a/api/src/state.rs b/api/src/state.rs index 02b9a85..a6329af 100644 --- a/api/src/state.rs +++ b/api/src/state.rs @@ -3,6 +3,8 @@ //! Holds shared state for the application. use axum::extract::FromRef; +#[cfg(feature = "auth-oidc")] +use infra::auth::oidc::OidcService; use std::sync::Arc; use crate::config::Config; @@ -11,15 +13,36 @@ use domain::UserService; #[derive(Clone)] pub struct AppState { pub user_service: Arc, + #[cfg(feature = "auth-oidc")] + pub oidc_service: Option>, + pub config: Arc, } impl AppState { - pub fn new(user_service: UserService, config: Config) -> Self { - Self { + pub async fn new(user_service: UserService, config: Config) -> anyhow::Result { + #[cfg(feature = "auth-oidc")] + let oidc_service = if let (Some(issuer), Some(id), Some(secret), Some(redirect)) = ( + &config.oidc_issuer, + &config.oidc_client_id, + &config.oidc_client_secret, + &config.oidc_redirect_url, + ) { + tracing::info!("Initializing OIDC service with issuer: {}", issuer); + Some(Arc::new( + OidcService::new(issuer.clone(), id.clone(), secret.clone(), redirect.clone()) + .await?, + )) + } else { + None + }; + + Ok(Self { user_service: Arc::new(user_service), + #[cfg(feature = "auth-oidc")] + oidc_service, config: Arc::new(config), - } + }) } } diff --git a/infra/Cargo.toml b/infra/Cargo.toml index fc49537..ddb9095 100644 --- a/infra/Cargo.toml +++ b/infra/Cargo.toml @@ -19,6 +19,7 @@ postgres = [ ] broker-nats = ["dep:futures-util", "k-core/broker-nats"] auth-axum-login = ["dep:axum-login", "dep:password-auth"] +auth-oidc = ["dep:openidconnect", "dep:url"] [dependencies] k-core = { git = "https://git.gabrielkaszewski.dev/GKaszewski/k-core", features = [ @@ -47,3 +48,6 @@ tower-sessions = "0.14" # Auth dependencies (optional) axum-login = { version = "0.18", optional = true } password-auth = { version = "1.0", optional = true } +openidconnect = { version = "4.0.1", optional = true } +url = { version = "2.5.8", optional = true } +# reqwest = { version = "0.13.1", features = ["blocking", "json"], optional = true } diff --git a/infra/src/auth/mod.rs b/infra/src/auth/mod.rs index e7c3fb9..f00a3b0 100644 --- a/infra/src/auth/mod.rs +++ b/infra/src/auth/mod.rs @@ -115,3 +115,6 @@ pub mod backend { Ok(auth_layer) } } + +#[cfg(feature = "auth-oidc")] +pub mod oidc; diff --git a/infra/src/auth/oidc.rs b/infra/src/auth/oidc.rs new file mode 100644 index 0000000..c6ac845 --- /dev/null +++ b/infra/src/auth/oidc.rs @@ -0,0 +1,145 @@ +use anyhow::anyhow; +use openidconnect::{ + AccessTokenHash, AuthorizationCode, Client, ClientId, ClientSecret, CsrfToken, + EmptyAdditionalClaims, EndpointMaybeSet, EndpointNotSet, EndpointSet, IssuerUrl, Nonce, + OAuth2TokenResponse, PkceCodeChallenge, PkceCodeVerifier, RedirectUrl, Scope, + StandardErrorResponse, TokenResponse, + core::{ + CoreAuthDisplay, CoreAuthPrompt, CoreAuthenticationFlow, CoreClient, CoreErrorResponseType, + CoreGenderClaim, CoreJsonWebKey, CoreJweContentEncryptionAlgorithm, CoreProviderMetadata, + CoreRevocableToken, CoreRevocationErrorResponse, CoreTokenIntrospectionResponse, + CoreTokenResponse, + }, + reqwest, +}; + +pub type OidcClient = Client< + EmptyAdditionalClaims, + CoreAuthDisplay, + CoreGenderClaim, + CoreJweContentEncryptionAlgorithm, + CoreJsonWebKey, + CoreAuthPrompt, + StandardErrorResponse, + CoreTokenResponse, + CoreTokenIntrospectionResponse, + CoreRevocableToken, + CoreRevocationErrorResponse, + EndpointSet, // HasAuthUrl (Required and guaranteed by discovery) + EndpointNotSet, // HasDeviceAuthUrl + EndpointNotSet, // HasIntrospectionUrl + EndpointNotSet, // HasRevocationUrl + EndpointMaybeSet, // HasTokenUrl (Discovered, might be missing) + EndpointMaybeSet, // HasUserInfoUrl (Discovered, might be missing) +>; + +#[derive(Clone)] +pub struct OidcService { + client: OidcClient, +} + +#[derive(Debug)] +pub struct OidcUser { + pub subject: String, + pub email: String, +} + +impl OidcService { + //todo: replace Strings with newtypes + pub async fn new( + issuer: String, + client_id: String, + client_secret: String, + redirect_url: String, + ) -> anyhow::Result { + let http_client = reqwest::ClientBuilder::new() + .redirect(reqwest::redirect::Policy::none()) + .build()?; + + let provider_metadata = + CoreProviderMetadata::discover_async(IssuerUrl::new(issuer)?, &http_client).await?; + + let client = CoreClient::from_provider_metadata( + provider_metadata, + ClientId::new(client_id), + Some(ClientSecret::new(client_secret)), + ) + .set_redirect_uri(RedirectUrl::new(redirect_url)?); + + Ok(Self { client }) + } + + // todo: replace this tuple with newtype + pub fn get_authorization_url(&self) -> (String, String, String, String) { + let (pkce_challenge, pkce_verifier) = PkceCodeChallenge::new_random_sha256(); + + let (auth_url, csrf_token, nonce) = self + .client + .authorize_url( + CoreAuthenticationFlow::AuthorizationCode, + CsrfToken::new_random, + Nonce::new_random, + ) + .add_scope(Scope::new("profile".to_string())) + .add_scope(Scope::new("email".to_string())) + .set_pkce_challenge(pkce_challenge) + .url(); + + ( + auth_url.to_string(), + csrf_token.secret().to_string(), + nonce.secret().to_string(), + pkce_verifier.secret().to_string(), + ) + } + + //todo: replace strings with newtype + pub async fn resolve_callback( + &self, + code: String, + nonce: String, + pkce_verifier: String, + ) -> anyhow::Result { + let http_client = reqwest::ClientBuilder::new() + .redirect(reqwest::redirect::Policy::none()) + .build()?; + + let pkce_verifier = PkceCodeVerifier::new(pkce_verifier); + let nonce = Nonce::new(nonce); + + let token_response = self + .client + .exchange_code(AuthorizationCode::new(code))? + .set_pkce_verifier(pkce_verifier) + .request_async(&http_client) + .await?; + + let id_token = token_response + .id_token() + .ok_or_else(|| anyhow!("Server did not return an ID token"))?; + + let id_token_verifier = self.client.id_token_verifier(); + let claims = id_token.claims(&id_token_verifier, &nonce)?; + + if let Some(expected_access_token_hash) = claims.access_token_hash() { + let actual_access_token_hash = AccessTokenHash::from_token( + token_response.access_token(), + id_token.signing_alg()?, + id_token.signing_key(&id_token_verifier)?, + )?; + + if actual_access_token_hash != *expected_access_token_hash { + return Err(anyhow!("Invalid access token")); + } + } + + Ok(OidcUser { + subject: claims.subject().to_string(), + email: claims + .email() + .map(|email| email.as_str()) + .unwrap_or("") + .to_string(), + }) + } +}