From f6e4eec00d7e3d20d90b5e849f6bff816cbb1f79 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Fri, 20 Sep 2024 01:27:10 -0400 Subject: [PATCH] refactor package --- Cargo.lock | 245 +++++++++++++----- .../lib/api_clients/google_sign_in/Cargo.toml | 2 + .../src/{ => certs}/jwk_to_public_key.rs | 8 +- .../google_sign_in/src/certs/key_map.rs | 6 + .../google_sign_in/src/certs/mod.rs | 2 + .../google_sign_in/src/claims/claims.rs | 29 +++ .../src/claims/google_custom_claims.rs | 63 +++++ .../google_sign_in/src/claims/mod.rs | 2 + .../src/decode_and_verify_token_claims.rs | 98 +++++++ .../google_sign_in/src/decode_token.rs | 153 ----------- .../google_sign_in/src/download_certs.rs | 22 ++ .../lib/api_clients/google_sign_in/src/lib.rs | 6 +- 12 files changed, 406 insertions(+), 230 deletions(-) rename crates/lib/api_clients/google_sign_in/src/{ => certs}/jwk_to_public_key.rs (92%) create mode 100644 crates/lib/api_clients/google_sign_in/src/certs/key_map.rs create mode 100644 crates/lib/api_clients/google_sign_in/src/certs/mod.rs create mode 100644 crates/lib/api_clients/google_sign_in/src/claims/claims.rs create mode 100644 crates/lib/api_clients/google_sign_in/src/claims/google_custom_claims.rs create mode 100644 crates/lib/api_clients/google_sign_in/src/claims/mod.rs create mode 100644 crates/lib/api_clients/google_sign_in/src/decode_and_verify_token_claims.rs delete mode 100644 crates/lib/api_clients/google_sign_in/src/decode_token.rs create mode 100644 crates/lib/api_clients/google_sign_in/src/download_certs.rs diff --git a/Cargo.lock b/Cargo.lock index d8f3c35af2..2eebab2223 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -838,7 +838,7 @@ dependencies = [ "pin-project-lite", "rustversion", "serde", - "sync_wrapper", + "sync_wrapper 0.1.2", "tower", "tower-layer", "tower-service", @@ -3021,6 +3021,7 @@ dependencies = [ "errors", "jwt-simple", "my-workspace-hack", + "reqwest 0.12.7", "serde", "serde_derive", "serde_json", @@ -3413,19 +3414,20 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.26.0" +version = "0.27.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c" +checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" dependencies = [ "futures-util", "http 1.1.0", "hyper 1.3.1", "hyper-util", - "rustls 0.22.4", + "rustls 0.23.13", "rustls-pki-types", "tokio", "tokio-rustls", "tower-service", + "webpki-roots 0.26.2", ] [[package]] @@ -3673,7 +3675,7 @@ dependencies = [ "rand 0.8.5", "redis_common", "redis_schema", - "reqwest 0.12.4", + "reqwest 0.12.7", "serde", "serde_derive", "serde_json", @@ -4568,7 +4570,7 @@ dependencies = [ "regex", "regex-automata", "regex-syntax", - "reqwest 0.12.4", + "reqwest 0.12.7", "rustls-pki-types", "semver 1.0.23", "serde", @@ -5093,7 +5095,7 @@ dependencies = [ "libc", "redox_syscall 0.5.2", "smallvec 1.13.2", - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -5518,6 +5520,54 @@ dependencies = [ "serde", ] +[[package]] +name = "quinn" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c7c5fdde3cdae7203427dc4f0a68fe0ed09833edc525a03456b153b79828684" +dependencies = [ + "bytes 1.6.0", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls 0.23.13", + "socket2", + "thiserror", + "tokio", + "tracing", +] + +[[package]] +name = "quinn-proto" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fadfaed2cd7f389d0161bb73eeb07b7b78f8691047a6f3e73caaeae55310a4a6" +dependencies = [ + "bytes 1.6.0", + "rand 0.8.5", + "ring", + "rustc-hash", + "rustls 0.23.13", + "slab", + "thiserror", + "tinyvec", + "tracing", +] + +[[package]] +name = "quinn-udp" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bffec3605b73c6f1754535084a85229fa8a30f86014e6c81aeec4abb68b0285" +dependencies = [ + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.52.0", +] + [[package]] name = "quote" version = "1.0.36" @@ -5908,8 +5958,8 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "sync_wrapper", - "system-configuration", + "sync_wrapper 0.1.2", + "system-configuration 0.5.1", "tokio", "tokio-native-tls", "tokio-util", @@ -5918,14 +5968,14 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "winreg 0.50.0", + "winreg", ] [[package]] name = "reqwest" -version = "0.12.4" +version = "0.12.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10" +checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63" dependencies = [ "base64 0.22.1", "bytes 1.6.0", @@ -5949,14 +5999,15 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls 0.22.4", + "quinn", + "rustls 0.23.13", "rustls-pemfile 2.1.2", "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", - "sync_wrapper", - "system-configuration", + "sync_wrapper 1.0.1", + "system-configuration 0.6.1", "tokio", "tokio-native-tls", "tokio-rustls", @@ -5966,7 +6017,7 @@ dependencies = [ "wasm-bindgen-futures", "web-sys", "webpki-roots 0.26.2", - "winreg 0.52.0", + "windows-registry", ] [[package]] @@ -6157,6 +6208,12 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustc-hash" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" + [[package]] name = "rustc_version" version = "0.2.3" @@ -6201,14 +6258,14 @@ dependencies = [ [[package]] name = "rustls" -version = "0.22.4" +version = "0.23.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" +checksum = "f2dabaac7466917e566adb06783a81ca48944c6898a1b08b9374106dd671f4c8" dependencies = [ - "log", + "once_cell", "ring", "rustls-pki-types", - "rustls-webpki 0.102.4", + "rustls-webpki 0.102.8", "subtle", "zeroize", ] @@ -6250,9 +6307,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.102.4" +version = "0.102.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff448f7e92e913c4b7d4c6d8e4540a1724b319b4152b8aef6d4cf8339712b33e" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" dependencies = [ "ring", "rustls-pki-types", @@ -7137,7 +7194,7 @@ dependencies = [ "redis_common", "redis_schema", "regex", - "reqwest 0.12.4", + "reqwest 0.12.7", "reusable_types", "rust-s3", "serde", @@ -7480,6 +7537,15 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +[[package]] +name = "sync_wrapper" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +dependencies = [ + "futures-core", +] + [[package]] name = "system-configuration" version = "0.5.1" @@ -7488,7 +7554,18 @@ checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" dependencies = [ "bitflags 1.3.2", "core-foundation", - "system-configuration-sys", + "system-configuration-sys 0.5.0", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.5.0", + "core-foundation", + "system-configuration-sys 0.6.0", ] [[package]] @@ -7501,6 +7578,16 @@ dependencies = [ "libc", ] +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "system-deps" version = "6.2.2" @@ -7594,7 +7681,7 @@ dependencies = [ "easyenv", "log", "my-workspace-hack", - "reqwest 0.12.4", + "reqwest 0.12.7", "serde_json", ] @@ -7796,11 +7883,11 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.25.0" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls 0.22.4", + "rustls 0.23.13", "rustls-pki-types", "tokio", ] @@ -8101,7 +8188,7 @@ dependencies = [ "my-workspace-hack", "mysql_queries", "r2d2_redis", - "reqwest 0.12.4", + "reqwest 0.12.7", "serde", "serde_derive", "serde_json", @@ -8292,7 +8379,7 @@ dependencies = [ "actix-web", "mime_guess", "regex", - "reqwest 0.12.4", + "reqwest 0.12.7", "rust-embed", "serde", "serde_json", @@ -8572,7 +8659,7 @@ dependencies = [ "log", "my-workspace-hack", "once_cell", - "reqwest 0.12.4", + "reqwest 0.12.7", "rss", "scraper", "serde", @@ -8662,7 +8749,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" dependencies = [ "windows-core 0.52.0", - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -8680,7 +8767,37 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.5", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets 0.52.6", ] [[package]] @@ -8698,7 +8815,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -8718,18 +8835,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.5", - "windows_aarch64_msvc 0.52.5", - "windows_i686_gnu 0.52.5", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.5", - "windows_x86_64_gnu 0.52.5", - "windows_x86_64_gnullvm 0.52.5", - "windows_x86_64_msvc 0.52.5", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -8740,9 +8857,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" @@ -8752,9 +8869,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" @@ -8764,15 +8881,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" @@ -8782,9 +8899,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" @@ -8794,9 +8911,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" @@ -8806,9 +8923,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" @@ -8818,9 +8935,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" @@ -8850,16 +8967,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "winreg" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" -dependencies = [ - "cfg-if 1.0.0", - "windows-sys 0.48.0", -] - [[package]] name = "ws2_32-sys" version = "0.2.1" diff --git a/crates/lib/api_clients/google_sign_in/Cargo.toml b/crates/lib/api_clients/google_sign_in/Cargo.toml index c09b04c1d1..90e4a719e4 100644 --- a/crates/lib/api_clients/google_sign_in/Cargo.toml +++ b/crates/lib/api_clients/google_sign_in/Cargo.toml @@ -29,6 +29,8 @@ errors = { path = "../../errors" } jwt-simple = { version = "0.12.10" , default-features=false, features=["pure-rust"] } base64 = "0.13" +reqwest = "0.12.7" + # Serialization serde = "1.0.125" serde_derive = "1.0.125" diff --git a/crates/lib/api_clients/google_sign_in/src/jwk_to_public_key.rs b/crates/lib/api_clients/google_sign_in/src/certs/jwk_to_public_key.rs similarity index 92% rename from crates/lib/api_clients/google_sign_in/src/jwk_to_public_key.rs rename to crates/lib/api_clients/google_sign_in/src/certs/jwk_to_public_key.rs index fdf92ddf8c..193c22161f 100644 --- a/crates/lib/api_clients/google_sign_in/src/jwk_to_public_key.rs +++ b/crates/lib/api_clients/google_sign_in/src/certs/jwk_to_public_key.rs @@ -1,11 +1,7 @@ +use crate::certs::key_map::KeyMap; use errors::{anyhow, AnyhowResult}; use jwt_simple::prelude::RS256PublicKey; use serde_derive::Deserialize; -use std::collections::HashMap; - -/// Map of JWK `kid` to `RS256PublicKey`. -/// (JWK sets typically contain more than one key.) -pub type KeyMap = HashMap; /// Parse Google's JWK keys into usable public keys. /// Grab Google's keys from here: https://www.googleapis.com/oauth2/v3/certs @@ -67,7 +63,7 @@ pub struct JsonKey { #[cfg(test)] mod tests { - use crate::jwk_to_public_key::jwk_to_public_key; + use crate::certs::jwk_to_public_key::jwk_to_public_key; use jwt_simple::algorithms::RSAPublicKeyLike; use std::fs::read_to_string; use testing::test_file_path::test_file_path; diff --git a/crates/lib/api_clients/google_sign_in/src/certs/key_map.rs b/crates/lib/api_clients/google_sign_in/src/certs/key_map.rs new file mode 100644 index 0000000000..de3af24ee4 --- /dev/null +++ b/crates/lib/api_clients/google_sign_in/src/certs/key_map.rs @@ -0,0 +1,6 @@ +use std::collections::HashMap; +use jwt_simple::algorithms::RS256PublicKey; + +/// Map of JWK `kid` to `RS256PublicKey`. +/// (JWK sets typically contain more than one key.) +pub type KeyMap = HashMap; diff --git a/crates/lib/api_clients/google_sign_in/src/certs/mod.rs b/crates/lib/api_clients/google_sign_in/src/certs/mod.rs new file mode 100644 index 0000000000..1f9a37d5c0 --- /dev/null +++ b/crates/lib/api_clients/google_sign_in/src/certs/mod.rs @@ -0,0 +1,2 @@ +pub mod jwk_to_public_key; +pub mod key_map; \ No newline at end of file diff --git a/crates/lib/api_clients/google_sign_in/src/claims/claims.rs b/crates/lib/api_clients/google_sign_in/src/claims/claims.rs new file mode 100644 index 0000000000..db94613f5c --- /dev/null +++ b/crates/lib/api_clients/google_sign_in/src/claims/claims.rs @@ -0,0 +1,29 @@ +use crate::claims::google_custom_claims::GoogleCustomClaims; +use errors::{anyhow, AnyhowResult}; +use jwt_simple::claims::{Audiences, JWTClaims}; + +pub struct Claims { + pub (crate) claims: JWTClaims, +} + +impl Claims { + pub fn email(&self) -> Option<&str> { + self.claims.custom.email.as_deref() + } + + pub fn email_verified(&self) -> bool { + self.claims.custom.email_verified.unwrap_or(false) + } + + /// Determine if the claim audience is as expected. + /// This is necessary so third parties don't send claims signed on their behalf by Google. + pub fn audience_matches(&self, audience: &str) -> AnyhowResult { + match self.claims.audiences.as_ref() { + Some(Audiences::AsString(claim_audience)) => Ok(claim_audience.eq(audience)), + Some(Audiences::AsSet(audiences)) => Ok(audiences.contains(audience)), + _ => Err(anyhow!("Invalid audience type")), + } + } + + // TODO(bt,2024-09-20): Other fields if important. +} diff --git a/crates/lib/api_clients/google_sign_in/src/claims/google_custom_claims.rs b/crates/lib/api_clients/google_sign_in/src/claims/google_custom_claims.rs new file mode 100644 index 0000000000..fa04797d28 --- /dev/null +++ b/crates/lib/api_clients/google_sign_in/src/claims/google_custom_claims.rs @@ -0,0 +1,63 @@ +use serde_derive::{Deserialize, Serialize}; + +/// Extra Payload of Google Sign In JWT +/// https://developers.google.com/identity/openid-connect/openid-connect +/// https://stackoverflow.com/questions/31056412/what-all-these-fields-mean +#[derive(Deserialize, Serialize, Clone, Debug)] +pub struct GoogleCustomClaims { + /// The user's email address. Provided only if you included the email scope in your + /// request. The value of this claim may not be unique to this account and could + /// change over time, therefore you should not use this value as the primary identifier + /// to link to your user record. You also can't rely on the domain of the email claim + /// to identify users of Google Workspace or Cloud organizations; use the hd claim instead. + pub email: Option, + + /// True if the user's e-mail address has been verified; otherwise false. + pub email_verified: Option, + + /// The user's full name, in a displayable form. (See Google documentation) + pub name: Option, + + /// The user's given name(s) or first name(s). Might be provided when a name claim is present. + pub given_name: Option, + + /// The user's surname(s) or last name(s). Might be provided when a name claim is present. + pub family_name: Option, + + /// The client_id of the authorized presenter. This claim is only needed when the party + /// requesting the ID token is not the same as the audience of the ID token. This may be + /// the case at Google for hybrid apps where a web application and Android app have a + /// different OAuth 2.0 client_id but share the same Google APIs project. + pub azp: Option, + + /// An identifier for the user, unique among all Google accounts and never reused. + /// A Google account can have multiple email addresses at different points in time, + /// but the sub value is never changed. Use sub within your application as the + /// unique-identifier key for the user. Maximum length of 255 case-sensitive ASCII + /// characters. + pub sub: Option, + + /// The URL of the user's profile picture. Might be provided when: + /// - The request scope included the string "profile" + /// - The ID token is returned from a token refresh + /// When picture claims are present, you can use them to update your app's user records. + /// Note that this claim is never guaranteed to be present. + pub picture: Option, + + // Other fields are pulled out of the custom payload: + + // /// The audience that this ID token is intended for. It must be one of the + // /// OAuth 2.0 client IDs of your application. + // /// NB: You must check that this matches the audience (the app client id) + // pub aud: Option, + + // /// The Issuer Identifier for the Issuer of the response. + // /// Always `https://accounts.google.com` or `accounts.google.com` for Google ID tokens. + // /// NB: You must check that this matches the issuer. + // pub iss: Option, + + // pub nbf: Option, + // pub jti: Option, + // pub iat: Option, + // pub exp: Option, +} diff --git a/crates/lib/api_clients/google_sign_in/src/claims/mod.rs b/crates/lib/api_clients/google_sign_in/src/claims/mod.rs new file mode 100644 index 0000000000..91509fea89 --- /dev/null +++ b/crates/lib/api_clients/google_sign_in/src/claims/mod.rs @@ -0,0 +1,2 @@ +pub mod claims; +pub mod google_custom_claims; \ No newline at end of file diff --git a/crates/lib/api_clients/google_sign_in/src/decode_and_verify_token_claims.rs b/crates/lib/api_clients/google_sign_in/src/decode_and_verify_token_claims.rs new file mode 100644 index 0000000000..3fbf596286 --- /dev/null +++ b/crates/lib/api_clients/google_sign_in/src/decode_and_verify_token_claims.rs @@ -0,0 +1,98 @@ +use crate::claims::claims::Claims; +use crate::claims::google_custom_claims::GoogleCustomClaims; +use errors::{anyhow, AnyhowResult}; +use jwt_simple::algorithms::RS256PublicKey; +use jwt_simple::algorithms::RSAPublicKeyLike; +use jwt_simple::common::VerificationOptions; +/* +Example payload: + iss https://accounts.google.com + azp 788843034237-uqcg8tbgofrcf1to37e1bqphd924jaf6.apps.googleusercontent.com + aud 788843034237-uqcg8tbgofrcf1to37e1bqphd924jaf6.apps.googleusercontent.com + sub 113101967612396793777 + email vocodes2020@gmail.com + email_verified true + nbf 1726786100 + name Vocodes Vocodes + picture https://lh3.googleusercontent.com/a/ACg8ocLz2-2OaAm0MQxR6j8CNr-Po8_Xr-aryATiCn4c0i_TuDmL_g=s96-c + given_name Vocodes + family_name Vocodes + iat 1726786400 + exp 1726790000 + jti 4d44eeac06ce79fc0ab2270cfeea30d8acf77613 + */ + +/// Decode a Google Sign In JWT. +/// Verification options can be supplied to increase clock skew tolerance, etc. +pub fn decode_and_verify_token_claims(key: &RS256PublicKey, token: &str, options: Option) -> AnyhowResult { + let claims = key.verify_token::(token, options)?; + + match claims.issuer.as_deref() { + Some("https://accounts.google.com" | "accounts.google.com") => {} // Permitted + _ => { + return Err(anyhow!("Invalid issuer: {:?}", claims.issuer)); + } + } + + Ok(Claims { + claims, + }) +} + +#[cfg(test)] +mod tests { + use crate::certs::jwk_to_public_key::jwk_to_public_key; + use crate::decode_and_verify_token_claims::decode_and_verify_token_claims; + use coarsetime::Duration; + use jwt_simple::claims::Audiences; + use jwt_simple::prelude::VerificationOptions; + use std::fs::read_to_string; + use testing::test_file_path::test_file_path; + + #[test] + fn test_decode() { + let jwk_file = test_file_path("test_data/crypto/google_signin.jwk").unwrap(); + let jwk_payload = read_to_string(jwk_file).unwrap(); + let key_map = jwk_to_public_key(&jwk_payload).unwrap(); + + let credential = "eyJhbGciOiJSUzI1NiIsImtpZCI6ImIyNjIwZDVlN2YxMzJiNTJhZmU4ODc1Y2RmMzc3NmMwNjQyNDlkMDQiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhenAiOiI3ODg4NDMwMzQyMzctdXFjZzh0YmdvZnJjZjF0bzM3ZTFicXBoZDkyNGphZjYuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJhdWQiOiI3ODg4NDMwMzQyMzctdXFjZzh0YmdvZnJjZjF0bzM3ZTFicXBoZDkyNGphZjYuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJzdWIiOiIxMTMxMDE5Njc2MTIzOTY3OTM3NzciLCJlbWFpbCI6InZvY29kZXMyMDIwQGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJuYmYiOjE3MjY3ODYxMDAsIm5hbWUiOiJWb2NvZGVzIFZvY29kZXMiLCJwaWN0dXJlIjoiaHR0cHM6Ly9saDMuZ29vZ2xldXNlcmNvbnRlbnQuY29tL2EvQUNnOG9jTHoyLTJPYUFtME1ReFI2ajhDTnItUG84X1hyLWFyeUFUaUNuNGMwaV9UdURtTF9nPXM5Ni1jIiwiZ2l2ZW5fbmFtZSI6IlZvY29kZXMiLCJmYW1pbHlfbmFtZSI6IlZvY29kZXMiLCJpYXQiOjE3MjY3ODY0MDAsImV4cCI6MTcyNjc5MDAwMCwianRpIjoiNGQ0NGVlYWMwNmNlNzlmYzBhYjIyNzBjZmVlYTMwZDhhY2Y3NzYxMyJ9.EYg71yIkvhxFGc8ZVCXeTOAmPAtLYDphHnkdf1sh8b_Jz4Y7S1DpmiTqQ1ytxu7J1xNixvdwhuIDzSlCvlxaFl8475GvAlyPTNtZtmWbFD5SRM_XHLOynijOp8WQ4nej-CHvT1KjjqMfkZ1EeQMoWk1H72PxPg_RiUgzsklkUs1wOkLAySk7R3EIAl7bIzpoY_WH2pxv9ccFpBtKDHaDqHkxAWBUQX0-G7ZXZBPVz07V28ZfdbzFDapjZaUFbumazh_-J2-9AA6JkcteF4h_gpbBcLYAuxt5bWI5FECWbYe42khwb93WJ5SK12Tt0EPoyzIObJs14NWGAajtHTg3wA"; + let key = key_map.get("b2620d5e7f132b52afe8875cdf3776c064249d04").unwrap(); + + // NB: This key won't work: + // let key = key_map.get("5aaff47c21d06e266cce395b2145c7c6d4730ea5").unwrap(); + + let options = VerificationOptions { + time_tolerance: Some(Duration::from_days(365 * 30)), + reject_before: None, + accept_future: false, + ..Default::default() + }; + + let claims = decode_and_verify_token_claims(key, credential, Some(options)).unwrap(); + + // Custom fields + assert_eq!(claims.claims.custom.email, Some("vocodes2020@gmail.com".to_string())); + assert_eq!(claims.claims.custom.email_verified, Some(true)); + assert_eq!(claims.claims.custom.azp, Some("788843034237-uqcg8tbgofrcf1to37e1bqphd924jaf6.apps.googleusercontent.com".to_string())); + assert_eq!(claims.claims.custom.name, Some("Vocodes Vocodes".to_string())); + assert_eq!(claims.claims.custom.picture, Some("https://lh3.googleusercontent.com/a/ACg8ocLz2-2OaAm0MQxR6j8CNr-Po8_Xr-aryATiCn4c0i_TuDmL_g=s96-c".to_string())); + assert_eq!(claims.claims.custom.given_name, Some("Vocodes".to_string())); + assert_eq!(claims.claims.custom.family_name, Some("Vocodes".to_string())); + + // Standard fields + assert_eq!(claims.claims.jwt_id, Some("4d44eeac06ce79fc0ab2270cfeea30d8acf77613".to_string())); + assert_eq!(claims.claims.issuer, Some("https://accounts.google.com".to_string())); + assert_eq!(claims.claims.subject, Some("113101967612396793777".to_string())); + assert_eq!(claims.claims.issued_at.unwrap().as_secs(), 1726786400); + + // Methods + assert_eq!(claims.email_verified(), true); + assert_eq!(claims.email(), Some("vocodes2020@gmail.com")); + assert_eq!(claims.audience_matches("788843034237-uqcg8tbgofrcf1to37e1bqphd924jaf6.apps.googleusercontent.com").unwrap(), true); + + match claims.claims.audiences.unwrap() { + Audiences::AsString(audience) => assert_eq!(audience, "788843034237-uqcg8tbgofrcf1to37e1bqphd924jaf6.apps.googleusercontent.com"), + Audiences::AsSet(_audiences) => panic!("Expected a single audience"), + } + } +} diff --git a/crates/lib/api_clients/google_sign_in/src/decode_token.rs b/crates/lib/api_clients/google_sign_in/src/decode_token.rs deleted file mode 100644 index 1d89c47a82..0000000000 --- a/crates/lib/api_clients/google_sign_in/src/decode_token.rs +++ /dev/null @@ -1,153 +0,0 @@ -use errors::AnyhowResult; -use jwt_simple::algorithms::RS256PublicKey; -use jwt_simple::algorithms::RSAPublicKeyLike; -use jwt_simple::claims::JWTClaims; -use jwt_simple::common::VerificationOptions; -use serde_derive::{Deserialize, Serialize}; - -/// Payload of Google Sign In -/// https://developers.google.com/identity/openid-connect/openid-connect -/// https://stackoverflow.com/questions/31056412/what-all-these-fields-mean -#[derive(Deserialize, Serialize, Clone, Debug)] -pub struct GoogleJwtPayloadExtraFields { - /// The user's email address. Provided only if you included the email scope in your - /// request. The value of this claim may not be unique to this account and could - /// change over time, therefore you should not use this value as the primary identifier - /// to link to your user record. You also can't rely on the domain of the email claim - /// to identify users of Google Workspace or Cloud organizations; use the hd claim instead. - pub email: Option, - - /// True if the user's e-mail address has been verified; otherwise false. - pub email_verified: Option, - - /// The user's full name, in a displayable form. (See Google documentation) - pub name: Option, - - /// The user's given name(s) or first name(s). Might be provided when a name claim is present. - pub given_name: Option, - - /// The user's surname(s) or last name(s). Might be provided when a name claim is present. - pub family_name: Option, - - /// The client_id of the authorized presenter. This claim is only needed when the party - /// requesting the ID token is not the same as the audience of the ID token. This may be - /// the case at Google for hybrid apps where a web application and Android app have a - /// different OAuth 2.0 client_id but share the same Google APIs project. - pub azp: Option, - - /// An identifier for the user, unique among all Google accounts and never reused. - /// A Google account can have multiple email addresses at different points in time, - /// but the sub value is never changed. Use sub within your application as the - /// unique-identifier key for the user. Maximum length of 255 case-sensitive ASCII - /// characters. - pub sub: Option, - - /// The URL of the user's profile picture. Might be provided when: - /// - The request scope included the string "profile" - /// - The ID token is returned from a token refresh - /// When picture claims are present, you can use them to update your app's user records. - /// Note that this claim is never guaranteed to be present. - pub picture: Option, - - // /// The audience that this ID token is intended for. It must be one of the - // /// OAuth 2.0 client IDs of your application. - // /// NB: You must check that this matches the audience (the app client id) - // pub aud: Option, - - // /// The Issuer Identifier for the Issuer of the response. - // /// Always `https://accounts.google.com` or `accounts.google.com` for Google ID tokens. - // /// NB: You must check that this matches the issuer. - // pub iss: Option, - - // pub nbf: Option, - // pub jti: Option, - // pub iat: Option, - // pub exp: Option, -} - -/* -Example payload: - iss https://accounts.google.com - azp 788843034237-uqcg8tbgofrcf1to37e1bqphd924jaf6.apps.googleusercontent.com - aud 788843034237-uqcg8tbgofrcf1to37e1bqphd924jaf6.apps.googleusercontent.com - sub 113101967612396793777 - email vocodes2020@gmail.com - email_verified true - nbf 1726786100 - name Vocodes Vocodes - picture https://lh3.googleusercontent.com/a/ACg8ocLz2-2OaAm0MQxR6j8CNr-Po8_Xr-aryATiCn4c0i_TuDmL_g=s96-c - given_name Vocodes - family_name Vocodes - iat 1726786400 - exp 1726790000 - jti 4d44eeac06ce79fc0ab2270cfeea30d8acf77613 - */ - -/// Decode a Google Sign In JWT. -/// Verification options can be supplied to increase clock skew tolerance, etc. -pub fn decode_token(key: &RS256PublicKey, token: &str, options: Option) -> AnyhowResult> { - let claims = key.verify_token::(token, options)?; - Ok(claims) -} - -#[cfg(test)] -mod tests { - use crate::decode_token::decode_token; - use crate::jwk_to_public_key::jwk_to_public_key; - use coarsetime::Duration; - use jwt_simple::claims::Audiences; - use jwt_simple::prelude::VerificationOptions; - use std::fs::read_to_string; - use testing::test_file_path::test_file_path; - - #[test] - fn test_decode() { - let jwk_file = test_file_path("test_data/crypto/google_signin.jwk").unwrap(); - let jwk_payload = read_to_string(jwk_file).unwrap(); - let key_map = jwk_to_public_key(&jwk_payload).unwrap(); - - let credential = "eyJhbGciOiJSUzI1NiIsImtpZCI6ImIyNjIwZDVlN2YxMzJiNTJhZmU4ODc1Y2RmMzc3NmMwNjQyNDlkMDQiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhenAiOiI3ODg4NDMwMzQyMzctdXFjZzh0YmdvZnJjZjF0bzM3ZTFicXBoZDkyNGphZjYuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJhdWQiOiI3ODg4NDMwMzQyMzctdXFjZzh0YmdvZnJjZjF0bzM3ZTFicXBoZDkyNGphZjYuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJzdWIiOiIxMTMxMDE5Njc2MTIzOTY3OTM3NzciLCJlbWFpbCI6InZvY29kZXMyMDIwQGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJuYmYiOjE3MjY3ODYxMDAsIm5hbWUiOiJWb2NvZGVzIFZvY29kZXMiLCJwaWN0dXJlIjoiaHR0cHM6Ly9saDMuZ29vZ2xldXNlcmNvbnRlbnQuY29tL2EvQUNnOG9jTHoyLTJPYUFtME1ReFI2ajhDTnItUG84X1hyLWFyeUFUaUNuNGMwaV9UdURtTF9nPXM5Ni1jIiwiZ2l2ZW5fbmFtZSI6IlZvY29kZXMiLCJmYW1pbHlfbmFtZSI6IlZvY29kZXMiLCJpYXQiOjE3MjY3ODY0MDAsImV4cCI6MTcyNjc5MDAwMCwianRpIjoiNGQ0NGVlYWMwNmNlNzlmYzBhYjIyNzBjZmVlYTMwZDhhY2Y3NzYxMyJ9.EYg71yIkvhxFGc8ZVCXeTOAmPAtLYDphHnkdf1sh8b_Jz4Y7S1DpmiTqQ1ytxu7J1xNixvdwhuIDzSlCvlxaFl8475GvAlyPTNtZtmWbFD5SRM_XHLOynijOp8WQ4nej-CHvT1KjjqMfkZ1EeQMoWk1H72PxPg_RiUgzsklkUs1wOkLAySk7R3EIAl7bIzpoY_WH2pxv9ccFpBtKDHaDqHkxAWBUQX0-G7ZXZBPVz07V28ZfdbzFDapjZaUFbumazh_-J2-9AA6JkcteF4h_gpbBcLYAuxt5bWI5FECWbYe42khwb93WJ5SK12Tt0EPoyzIObJs14NWGAajtHTg3wA"; - let key = key_map.get("b2620d5e7f132b52afe8875cdf3776c064249d04").unwrap(); - - // NB: This key won't work: - // let key = key_map.get("5aaff47c21d06e266cce395b2145c7c6d4730ea5").unwrap(); - - let options = VerificationOptions { - time_tolerance: Some(Duration::from_days(365 * 30)), - reject_before: None, - accept_future: false, - ..Default::default() - }; - - let claims = decode_token(key, credential, Some(options)).unwrap(); - - // Custom fields - assert_eq!(claims.custom.email, Some("vocodes2020@gmail.com".to_string())); - assert_eq!(claims.custom.email_verified, Some(true)); - assert_eq!(claims.custom.azp, Some("788843034237-uqcg8tbgofrcf1to37e1bqphd924jaf6.apps.googleusercontent.com".to_string())); - assert_eq!(claims.custom.name, Some("Vocodes Vocodes".to_string())); - assert_eq!(claims.custom.picture, Some("https://lh3.googleusercontent.com/a/ACg8ocLz2-2OaAm0MQxR6j8CNr-Po8_Xr-aryATiCn4c0i_TuDmL_g=s96-c".to_string())); - assert_eq!(claims.custom.given_name, Some("Vocodes".to_string())); - assert_eq!(claims.custom.family_name, Some("Vocodes".to_string())); - - // TODO: Why aren't these being decoded? - //assert_eq!(claims.custom.sub, Some("113101967612396793777".to_string())); - //assert_eq!(claims.custom.iss, Some("https://accounts.google.com".to_string())); - //assert_eq!(claims.custom.aud, Some("788843034237-uqcg8tbgofrcf1to37e1bqphd924jaf6.apps.googleusercontent.com".to_string())); - //assert_eq!(claims.custom.nbf, Some("".to_string())); - //assert_eq!(claims.custom.jti, Some("".to_string())); - //assert_eq!(claims.custom.iat, Some("".to_string())); - //assert_eq!(claims.custom.exp, Some("".to_string())); - - // Standard fields - assert_eq!(claims.jwt_id, Some("4d44eeac06ce79fc0ab2270cfeea30d8acf77613".to_string())); - assert_eq!(claims.issuer, Some("https://accounts.google.com".to_string())); - assert_eq!(claims.subject, Some("113101967612396793777".to_string())); - assert_eq!(claims.issued_at.unwrap().as_secs(), 1726786400); - - match claims.audiences.unwrap() { - Audiences::AsString(audience) => assert_eq!(audience, "788843034237-uqcg8tbgofrcf1to37e1bqphd924jaf6.apps.googleusercontent.com"), - Audiences::AsSet(_audiences) => panic!("Expected a single audience"), - } - } -} \ No newline at end of file diff --git a/crates/lib/api_clients/google_sign_in/src/download_certs.rs b/crates/lib/api_clients/google_sign_in/src/download_certs.rs new file mode 100644 index 0000000000..a07f8aeec9 --- /dev/null +++ b/crates/lib/api_clients/google_sign_in/src/download_certs.rs @@ -0,0 +1,22 @@ +use crate::certs::jwk_to_public_key::jwk_to_public_key; +use crate::certs::key_map::KeyMap; +use errors::{anyhow, AnyhowResult}; + +const GOOGLE_CERTS_URL : &str = "https://www.googleapis.com/oauth2/v3/certs"; + +pub async fn download_cert_key_set() -> AnyhowResult { + let certs = download_certs().await?; + let key_map = jwk_to_public_key(&certs)?; + Ok(key_map) +} + +pub async fn download_certs() -> AnyhowResult { + let response = reqwest::get(GOOGLE_CERTS_URL).await?; + + if !response.status().is_success() { + return Err(anyhow!("Failed to download Google certs: {:?}", response.status())); + } + + let body = response.text().await?; + Ok(body) +} diff --git a/crates/lib/api_clients/google_sign_in/src/lib.rs b/crates/lib/api_clients/google_sign_in/src/lib.rs index 31ac1169e0..5fc55402eb 100644 --- a/crates/lib/api_clients/google_sign_in/src/lib.rs +++ b/crates/lib/api_clients/google_sign_in/src/lib.rs @@ -18,5 +18,7 @@ #![allow(dead_code)] #![allow(non_snake_case)] -pub mod decode_token; -pub mod jwk_to_public_key; +pub mod certs; +pub mod claims; +pub mod decode_and_verify_token_claims; +pub mod download_certs;