refactor package

This commit is contained in:
Brandon Thomas committed 2024-09-20 01:27:10 -04:00
1 parent 8b1f4b3005
commit f6e4eec00d
12 files changed
+406 -230

No files matched your search

Generated
+176 -69
View File
@@ -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"
@@ -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"
@@ -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<String, RS256PublicKey>;
/// 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;
@@ -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<String, RS256PublicKey>;
@@ -0,0 +1,2 @@
pub mod jwk_to_public_key;
pub mod key_map;
@@ -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<GoogleCustomClaims>,
}
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<bool> {
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.
}
@@ -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<String>,
/// True if the user's e-mail address has been verified; otherwise false.
pub email_verified: Option<bool>,
/// The user's full name, in a displayable form. (See Google documentation)
pub name: Option<String>,
/// The user's given name(s) or first name(s). Might be provided when a name claim is present.
pub given_name: Option<String>,
/// The user's surname(s) or last name(s). Might be provided when a name claim is present.
pub family_name: Option<String>,
/// 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<String>,
/// 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<String>,
/// 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<String>,
// 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<String>,
// /// 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<String>,
// pub nbf: Option<String>,
// pub jti: Option<String>,
// pub iat: Option<String>,
// pub exp: Option<String>,
}
@@ -0,0 +1,2 @@
pub mod claims;
pub mod google_custom_claims;
@@ -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<VerificationOptions>) -> AnyhowResult<Claims> {
let claims = key.verify_token::<GoogleCustomClaims>(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"),
}
}
}
@@ -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<String>,
/// True if the user's e-mail address has been verified; otherwise false.
pub email_verified: Option<bool>,
/// The user's full name, in a displayable form. (See Google documentation)
pub name: Option<String>,
/// The user's given name(s) or first name(s). Might be provided when a name claim is present.
pub given_name: Option<String>,
/// The user's surname(s) or last name(s). Might be provided when a name claim is present.
pub family_name: Option<String>,
/// 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<String>,
/// 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<String>,
/// 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<String>,
// /// 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<String>,
// /// 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<String>,
// pub nbf: Option<String>,
// pub jti: Option<String>,
// pub iat: Option<String>,
// pub exp: Option<String>,
}
/*
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<VerificationOptions>) -> AnyhowResult<JWTClaims<GoogleJwtPayloadExtraFields>> {
let claims = key.verify_token::<GoogleJwtPayloadExtraFields>(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"),
}
}
}
@@ -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<KeyMap> {
let certs = download_certs().await?;
let key_map = jwk_to_public_key(&certs)?;
Ok(key_map)
}
pub async fn download_certs() -> AnyhowResult<String> {
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)
}
@@ -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;