From 10ee18526046801bf09f5f79c052b7a2cfd01bae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Nieto?= Date: Wed, 8 Jun 2022 14:45:07 +0200 Subject: [PATCH 1/3] Update lib.rs changed sub to aud --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 7411826..f45e357 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -93,7 +93,7 @@ pub async fn validate( return Err(Error::IssClaimMismatch); } - if token_data.claims.sub != client_id { + if token_data.claims.aud != client_id { return Err(Error::ClientIdMismatch); } Ok(token_data) From a15f6df0cfd3bd351fb582f9a221a2f4933472d2 Mon Sep 17 00:00:00 2001 From: Frederic Nieto Date: Wed, 8 Jun 2022 15:52:20 +0200 Subject: [PATCH 2/3] added key cache for apple --- Cargo.toml | 4 +- src/lib.rs | 268 ++++++++++++++++++++++++++++------------------------- 2 files changed, 145 insertions(+), 127 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 996e357..5fed32a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,4 +24,6 @@ serde_json = "1.0" hyper = { version = "0.14", features = ["http1"] } hyper-tls = "0.5" tokio = { version = "1", features = ["rt-multi-thread","net","macros"] } -thiserror = "1.0" \ No newline at end of file +thiserror = "1.0" +lazy_static = "1.4.0" +async_once = "0.2.6" diff --git a/src/lib.rs b/src/lib.rs index f45e357..7fff7e5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,168 +19,184 @@ use error::Result; use hyper::{body, Body, Client, Request}; use hyper_tls::HttpsConnector; use jsonwebtoken::{ - self, decode, decode_header, DecodingKey, TokenData, Validation, + self, decode, decode_header, DecodingKey, TokenData, Validation, }; use serde::de::DeserializeOwned; use std::collections::HashMap; +use lazy_static::lazy_static; +use async_once::AsyncOnce; +use tokio::time::sleep; +use core::time::Duration; + +lazy_static! { + static ref APPLE_KEYS : AsyncOnce> = AsyncOnce::new(async { + loop { + if let Ok(keys) = fetch_apple_keys().await { + break keys; + } else { + sleep(Duration::from_secs(1)).await + } + } + }); + } -//TODO: put verification into a struct and only fetch apple keys once in the beginning async fn fetch_apple_keys() -> Result> { - let https = HttpsConnector::new(); - let client = Client::builder().build::<_, hyper::Body>(https); - - let req = Request::builder() - .method("GET") - .uri(APPLE_PUB_KEYS) - .body(Body::from(""))?; - - let resp = client.request(req).await?; - let buf = body::to_bytes(resp).await?; - - let mut resp: HashMap> = - serde_json::from_slice(&buf)?; - - resp.remove("keys").map_or(Err(Error::AppleKeys), |res| { - Ok(res - .into_iter() - .map(|val| (val.kid.clone(), val)) - .collect::>()) - }) + let https = HttpsConnector::new(); + let client = Client::builder().build::<_, hyper::Body>(https); + + let req = Request::builder() + .method("GET") + .uri(APPLE_PUB_KEYS) + .body(Body::from(""))?; + + let resp = client.request(req).await?; + let buf = body::to_bytes(resp).await?; + + let mut resp: HashMap> = + serde_json::from_slice(&buf)?; + + resp.remove("keys").map_or(Err(Error::AppleKeys), |res| { + Ok(res + .into_iter() + .map(|val| (val.kid.clone(), val)) + .collect::>()) + }) } /// decoe token with optional expiry validation pub async fn decode_token( - token: String, - ignore_expire: bool, + token: &str, + ignore_expire: bool, ) -> Result> { - let header = decode_header(token.as_str())?; - - let kid = match header.kid { - Some(k) => k, - None => return Err(Error::KidNotFound), - }; - - let pubkeys = fetch_apple_keys().await?; - - let pubkey = match pubkeys.get(&kid) { - Some(key) => key, - None => return Err(Error::KeyNotFound), - }; - - let mut val = Validation::new(header.alg); - val.validate_exp = !ignore_expire; - let token_data = decode::( - token.as_str(), - &DecodingKey::from_rsa_components(&pubkey.n, &pubkey.e) - .unwrap(), - &val, - )?; - - Ok(token_data) + let header = decode_header(token)?; + + let kid = match header.kid { + Some(k) => k, + None => return Err(Error::KidNotFound), + }; + + let pubkeys = APPLE_KEYS.get().await; + + let pubkey = match pubkeys.get(&kid) { + Some(key) => key, + None => return Err(Error::KeyNotFound), + }; + + let mut val = Validation::new(header.alg); + val.validate_exp = !ignore_expire; + let token_data = decode::( + token, + &DecodingKey::from_rsa_components(&pubkey.n, &pubkey.e) + .unwrap(), + &val, + )?; + + Ok(token_data) } pub async fn validate( - client_id: String, - token: String, - ignore_expire: bool, + client_id: &str, + token: &str, + ignore_expire: bool, ) -> Result> { - let token_data = - decode_token::(token, ignore_expire).await?; - - //TODO: can this be validated alread in `decode_token`? - if token_data.claims.iss != APPLE_ISSUER { - return Err(Error::IssClaimMismatch); - } - - if token_data.claims.aud != client_id { - return Err(Error::ClientIdMismatch); - } - Ok(token_data) + let token_data = + decode_token::(token, ignore_expire).await?; + + //TODO: can this be validated alread in `decode_token`? + if token_data.claims.iss != APPLE_ISSUER { + return Err(Error::IssClaimMismatch); + } + + if token_data.claims.aud != client_id { + return Err(Error::ClientIdMismatch); + } + Ok(token_data) } /// allows to check whether the `validate` result was errored because of an expired signature #[must_use] pub fn is_expired( - validate_result: &Result>, + validate_result: &Result>, ) -> bool { - if let Err(Error::Jwt(error)) = validate_result { - return matches!( + if let Err(Error::Jwt(error)) = validate_result { + return matches!( error.kind(), jsonwebtoken::errors::ErrorKind::ExpiredSignature ); - } + } - false + false } #[cfg(test)] mod tests { - use crate::{ - decode_token, is_expired, validate, ClaimsServer2Server, - Error, - }; + use crate::{ + decode_token, is_expired, validate, ClaimsServer2Server, + Error, + }; #[tokio::test] async fn validate_test() -> std::result::Result<(), Error> { let user_token = "001026.16112b36378440d995af22b268f00984.1744"; + let client_id = "com.gameroasters.stack4"; let token = "eyJraWQiOiJZdXlYb1kiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwiYXVkIjoiY29tLmdhbWVyb2FzdGVycy5zdGFjazQiLCJleHAiOjE2MTQ1MTc1OTQsImlhdCI6MTYxNDQzMTE5NCwic3ViIjoiMDAxMDI2LjE2MTEyYjM2Mzc4NDQwZDk5NWFmMjJiMjY4ZjAwOTg0LjE3NDQiLCJjX2hhc2giOiJNNVVDdW5GdTFKNjdhdVE2LXEta093IiwiZW1haWwiOiJ6ZGZ1N2p0dXVzQHByaXZhdGVyZWxheS5hcHBsZWlkLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjoidHJ1ZSIsImlzX3ByaXZhdGVfZW1haWwiOiJ0cnVlIiwiYXV0aF90aW1lIjoxNjE0NDMxMTk0LCJub25jZV9zdXBwb3J0ZWQiOnRydWV9.GuMJfVbnEvqppwwHFZjn3GDJtB4c4rl7C4PZzyDsdyiuXcFcXq52Ti0WSJBsqtfyT2dXvYxVxebHtONSQha_9DiM5qfYTZbpDDlIXrOMy1fkfStocold_wHWavofIpoJQVUMj45HLHtjixiNE903Pho6eY2UjEUjB3aFe8txuFIMv2JsaMCYzG4-e632FKBn63SroCkLc-8b4EVV4iYqnC5AfZArXhVjUevhhlaBH0E8Az2OGEe74U2WgBvMXEilmd62Ek-uInnrpJRgYQfYXvehQ1yT3aMiIgJICTQFMDdL1KAvs6mc081lNJLFYvViWlMH-Y7E5ajtUiMApiNYsg"; - let result = - validate(user_token.to_string(), token.to_string(), true) - .await?; + let result = + validate(client_id, token, true) + .await?; assert_eq!(result.claims.sub, user_token); - assert_eq!(result.claims.aud, "com.gameroasters.stack4"); - - Ok(()) - } - - #[tokio::test] - async fn validate_no_email() { - let token = "eyJraWQiOiJlWGF1bm1MIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwiYXVkIjoiY29tLmdhbWVyb2FzdGVycy5zdGFjazQiLCJleHAiOjE2MzA4Mjc4MzAsImlhdCI6MTYzMDc0MTQzMCwic3ViIjoiMDAxMDI2LjE2MTEyYjM2Mzc4NDQwZDk5NWFmMjJiMjY4ZjAwOTg0LjE3NDQiLCJjX2hhc2giOiI0QjZKWTU4TmstVUJsY3dMa2VLc2lnIiwiYXV0aF90aW1lIjoxNjMwNzQxNDMwLCJub25jZV9zdXBwb3J0ZWQiOnRydWV9.iW0xk__fPD0mlh9UU-vh9VnR8yekWq64sl5re5d7UmDJxb1Fzk1Kca-hkA_Ka1LhSmKADdFW0DYEZhckqh49DgFtFdx6hM9t7guK3yrvBglhF5LAyb8NR028npxioLTTIgP_aR6Bpy5AyLQrU-yYEx2WTPYV5ln9n8vW154gZKRyl2KBlj9fS11BL_X1UFbFrL21GG_iPbB4qt5ywwTPoJ-diGN5JQzP5fk4yU4e4YmHhxJrT0NTTux2mB3lGJLa6YN-JYe_BuVV9J-sg_2r_ugTOUp3xQpfntu8xgQrY5W0oPxAPM4sibNLsye2kgPYYxfRYowc0JIjOcOd_JHDbQ"; - - validate( - "001026.16112b36378440d995af22b268f00984.1744".into(), - token.to_string(), - true, - ) - .await - .unwrap(); - } - - #[tokio::test] - async fn validate_expired() { - let token = "eyJraWQiOiJlWGF1bm1MIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwiYXVkIjoiY29tLmdhbWVyb2FzdGVycy5zdGFjazQiLCJleHAiOjE2MzA4Mjc4MzAsImlhdCI6MTYzMDc0MTQzMCwic3ViIjoiMDAxMDI2LjE2MTEyYjM2Mzc4NDQwZDk5NWFmMjJiMjY4ZjAwOTg0LjE3NDQiLCJjX2hhc2giOiI0QjZKWTU4TmstVUJsY3dMa2VLc2lnIiwiYXV0aF90aW1lIjoxNjMwNzQxNDMwLCJub25jZV9zdXBwb3J0ZWQiOnRydWV9.iW0xk__fPD0mlh9UU-vh9VnR8yekWq64sl5re5d7UmDJxb1Fzk1Kca-hkA_Ka1LhSmKADdFW0DYEZhckqh49DgFtFdx6hM9t7guK3yrvBglhF5LAyb8NR028npxioLTTIgP_aR6Bpy5AyLQrU-yYEx2WTPYV5ln9n8vW154gZKRyl2KBlj9fS11BL_X1UFbFrL21GG_iPbB4qt5ywwTPoJ-diGN5JQzP5fk4yU4e4YmHhxJrT0NTTux2mB3lGJLa6YN-JYe_BuVV9J-sg_2r_ugTOUp3xQpfntu8xgQrY5W0oPxAPM4sibNLsye2kgPYYxfRYowc0JIjOcOd_JHDbQ"; - - let res = validate( - "001026.16112b36378440d995af22b268f00984.1744".into(), - token.to_string(), - false, - ) - .await; - - assert!(is_expired(&res)); - } - - #[tokio::test] - async fn test_server_to_server_payload() { - let token = "eyJraWQiOiJlWGF1bm1MIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwiYXVkIjoiY29tLmdhbWVyb2FzdGVycy5zdGFjazQiLCJleHAiOjE2MzAxNzE4MTIsImlhdCI6MTYzMDA4NTQxMiwianRpIjoiQjk0T2REMDNwRnNhWWFOLUZ0djdtQSIsImV2ZW50cyI6IntcInR5cGVcIjpcImVtYWlsLWRpc2FibGVkXCIsXCJzdWJcIjpcIjAwMTAyNi4xNjExMmIzNjM3ODQ0MGQ5OTVhZjIyYjI2OGYwMDk4NC4xNzQ0XCIsXCJldmVudF90aW1lXCI6MTYzMDA4NTQwMzY0OCxcImVtYWlsXCI6XCJ6ZGZ1N2p0dXVzQHByaXZhdGVyZWxheS5hcHBsZWlkLmNvbVwiLFwiaXNfcHJpdmF0ZV9lbWFpbFwiOlwidHJ1ZVwifSJ9.SSdUM88GHqrS0QXHtaehbPxLQkAB3s1-qzcy3i2iRoSCzDhA1Q3o_FhiCbqOsbiPDOQ9aA1Z8-oAz1p3-TMfHy6QdIs1vLxBmNTe5IazNJw_7wwDZG2nr-bsKPUQldE--tK1EUFXQqQxQbfjJJE0JFEwPib2rmnb-t0mRopKMx2wg3CUlI64BHI2O8giGCbWB7UbJs2BpcUuapVShCIR7Eqxy0_ud81CUDjKzZK2CcmSRGDIk8g9pRqOHmPUFMOrDjj6_hUR9mf-xCrCedoC9f05z_yKD026A4gWGFn4pxTP8-uDTRPxcONax_vnQHBUDigYi8HXuzWorTx2ORPjaw"; - - let result = decode_token::( - token.to_string(), - true, - ) - .await - .unwrap(); - - assert_eq!(result.claims.aud, "com.gameroasters.stack4"); - assert_eq!( - result.claims.events.sub, - "001026.16112b36378440d995af22b268f00984.1744" - ); - - println!("{:?}", result); - } + assert_eq!(result.claims.aud, client_id); + + Ok(()) + } + + #[tokio::test] + async fn validate_no_email() { + let token = "eyJraWQiOiJlWGF1bm1MIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwiYXVkIjoiY29tLmdhbWVyb2FzdGVycy5zdGFjazQiLCJleHAiOjE2MzA4Mjc4MzAsImlhdCI6MTYzMDc0MTQzMCwic3ViIjoiMDAxMDI2LjE2MTEyYjM2Mzc4NDQwZDk5NWFmMjJiMjY4ZjAwOTg0LjE3NDQiLCJjX2hhc2giOiI0QjZKWTU4TmstVUJsY3dMa2VLc2lnIiwiYXV0aF90aW1lIjoxNjMwNzQxNDMwLCJub25jZV9zdXBwb3J0ZWQiOnRydWV9.iW0xk__fPD0mlh9UU-vh9VnR8yekWq64sl5re5d7UmDJxb1Fzk1Kca-hkA_Ka1LhSmKADdFW0DYEZhckqh49DgFtFdx6hM9t7guK3yrvBglhF5LAyb8NR028npxioLTTIgP_aR6Bpy5AyLQrU-yYEx2WTPYV5ln9n8vW154gZKRyl2KBlj9fS11BL_X1UFbFrL21GG_iPbB4qt5ywwTPoJ-diGN5JQzP5fk4yU4e4YmHhxJrT0NTTux2mB3lGJLa6YN-JYe_BuVV9J-sg_2r_ugTOUp3xQpfntu8xgQrY5W0oPxAPM4sibNLsye2kgPYYxfRYowc0JIjOcOd_JHDbQ"; + + validate( + "com.gameroasters.stack4", + token, + true, + ) + .await + .unwrap(); + } + + #[tokio::test] + async fn validate_expired() { + let token = "eyJraWQiOiJlWGF1bm1MIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwiYXVkIjoiY29tLmdhbWVyb2FzdGVycy5zdGFjazQiLCJleHAiOjE2MzA4Mjc4MzAsImlhdCI6MTYzMDc0MTQzMCwic3ViIjoiMDAxMDI2LjE2MTEyYjM2Mzc4NDQwZDk5NWFmMjJiMjY4ZjAwOTg0LjE3NDQiLCJjX2hhc2giOiI0QjZKWTU4TmstVUJsY3dMa2VLc2lnIiwiYXV0aF90aW1lIjoxNjMwNzQxNDMwLCJub25jZV9zdXBwb3J0ZWQiOnRydWV9.iW0xk__fPD0mlh9UU-vh9VnR8yekWq64sl5re5d7UmDJxb1Fzk1Kca-hkA_Ka1LhSmKADdFW0DYEZhckqh49DgFtFdx6hM9t7guK3yrvBglhF5LAyb8NR028npxioLTTIgP_aR6Bpy5AyLQrU-yYEx2WTPYV5ln9n8vW154gZKRyl2KBlj9fS11BL_X1UFbFrL21GG_iPbB4qt5ywwTPoJ-diGN5JQzP5fk4yU4e4YmHhxJrT0NTTux2mB3lGJLa6YN-JYe_BuVV9J-sg_2r_ugTOUp3xQpfntu8xgQrY5W0oPxAPM4sibNLsye2kgPYYxfRYowc0JIjOcOd_JHDbQ"; + + let res = validate( + "com.gameroasters.stack4", + token, + false, + ) + .await; + + assert!(is_expired(&res)); + } + + #[tokio::test] + async fn test_server_to_server_payload() { + let token = "eyJraWQiOiJlWGF1bm1MIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwiYXVkIjoiY29tLmdhbWVyb2FzdGVycy5zdGFjazQiLCJleHAiOjE2MzAxNzE4MTIsImlhdCI6MTYzMDA4NTQxMiwianRpIjoiQjk0T2REMDNwRnNhWWFOLUZ0djdtQSIsImV2ZW50cyI6IntcInR5cGVcIjpcImVtYWlsLWRpc2FibGVkXCIsXCJzdWJcIjpcIjAwMTAyNi4xNjExMmIzNjM3ODQ0MGQ5OTVhZjIyYjI2OGYwMDk4NC4xNzQ0XCIsXCJldmVudF90aW1lXCI6MTYzMDA4NTQwMzY0OCxcImVtYWlsXCI6XCJ6ZGZ1N2p0dXVzQHByaXZhdGVyZWxheS5hcHBsZWlkLmNvbVwiLFwiaXNfcHJpdmF0ZV9lbWFpbFwiOlwidHJ1ZVwifSJ9.SSdUM88GHqrS0QXHtaehbPxLQkAB3s1-qzcy3i2iRoSCzDhA1Q3o_FhiCbqOsbiPDOQ9aA1Z8-oAz1p3-TMfHy6QdIs1vLxBmNTe5IazNJw_7wwDZG2nr-bsKPUQldE--tK1EUFXQqQxQbfjJJE0JFEwPib2rmnb-t0mRopKMx2wg3CUlI64BHI2O8giGCbWB7UbJs2BpcUuapVShCIR7Eqxy0_ud81CUDjKzZK2CcmSRGDIk8g9pRqOHmPUFMOrDjj6_hUR9mf-xCrCedoC9f05z_yKD026A4gWGFn4pxTP8-uDTRPxcONax_vnQHBUDigYi8HXuzWorTx2ORPjaw"; + + let result = decode_token::( + token, + true, + ) + .await + .unwrap(); + + assert_eq!(result.claims.aud, "com.gameroasters.stack4"); + assert_eq!( + result.claims.events.sub, + "001026.16112b36378440d995af22b268f00984.1744" + ); + + println!("{:?}", result); + } } From 4b8b6a1bfabb7646176e4e31cc14d037a39aa366 Mon Sep 17 00:00:00 2001 From: Frederic Nieto Date: Wed, 8 Jun 2022 15:58:50 +0200 Subject: [PATCH 3/3] reverted to project indentation --- src/lib.rs | 258 ++++++++++++++++++++++++++--------------------------- 1 file changed, 129 insertions(+), 129 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 7fff7e5..eaf1114 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,7 +19,7 @@ use error::Result; use hyper::{body, Body, Client, Request}; use hyper_tls::HttpsConnector; use jsonwebtoken::{ - self, decode, decode_header, DecodingKey, TokenData, Validation, + self, decode, decode_header, DecodingKey, TokenData, Validation, }; use serde::de::DeserializeOwned; use std::collections::HashMap; @@ -29,174 +29,174 @@ use tokio::time::sleep; use core::time::Duration; lazy_static! { - static ref APPLE_KEYS : AsyncOnce> = AsyncOnce::new(async { - loop { - if let Ok(keys) = fetch_apple_keys().await { + static ref APPLE_KEYS : AsyncOnce> = AsyncOnce::new(async { + loop { + if let Ok(keys) = fetch_apple_keys().await { break keys; } else { sleep(Duration::from_secs(1)).await } } - }); - } + }); +} async fn fetch_apple_keys() -> Result> { - let https = HttpsConnector::new(); - let client = Client::builder().build::<_, hyper::Body>(https); - - let req = Request::builder() - .method("GET") - .uri(APPLE_PUB_KEYS) - .body(Body::from(""))?; - - let resp = client.request(req).await?; - let buf = body::to_bytes(resp).await?; - - let mut resp: HashMap> = - serde_json::from_slice(&buf)?; - - resp.remove("keys").map_or(Err(Error::AppleKeys), |res| { - Ok(res - .into_iter() - .map(|val| (val.kid.clone(), val)) - .collect::>()) - }) + let https = HttpsConnector::new(); + let client = Client::builder().build::<_, hyper::Body>(https); + + let req = Request::builder() + .method("GET") + .uri(APPLE_PUB_KEYS) + .body(Body::from(""))?; + + let resp = client.request(req).await?; + let buf = body::to_bytes(resp).await?; + + let mut resp: HashMap> = + serde_json::from_slice(&buf)?; + + resp.remove("keys").map_or(Err(Error::AppleKeys), |res| { + Ok(res + .into_iter() + .map(|val| (val.kid.clone(), val)) + .collect::>()) + }) } /// decoe token with optional expiry validation pub async fn decode_token( - token: &str, - ignore_expire: bool, + token: &str, + ignore_expire: bool, ) -> Result> { - let header = decode_header(token)?; - - let kid = match header.kid { - Some(k) => k, - None => return Err(Error::KidNotFound), - }; - - let pubkeys = APPLE_KEYS.get().await; - - let pubkey = match pubkeys.get(&kid) { - Some(key) => key, - None => return Err(Error::KeyNotFound), - }; - - let mut val = Validation::new(header.alg); - val.validate_exp = !ignore_expire; - let token_data = decode::( - token, - &DecodingKey::from_rsa_components(&pubkey.n, &pubkey.e) - .unwrap(), - &val, - )?; - - Ok(token_data) + let header = decode_header(token)?; + + let kid = match header.kid { + Some(k) => k, + None => return Err(Error::KidNotFound), + }; + + let pubkeys = APPLE_KEYS.get().await; + + let pubkey = match pubkeys.get(&kid) { + Some(key) => key, + None => return Err(Error::KeyNotFound), + }; + + let mut val = Validation::new(header.alg); + val.validate_exp = !ignore_expire; + let token_data = decode::( + token, + &DecodingKey::from_rsa_components(&pubkey.n, &pubkey.e) + .unwrap(), + &val, + )?; + + Ok(token_data) } pub async fn validate( - client_id: &str, - token: &str, - ignore_expire: bool, + client_id: &str, + token: &str, + ignore_expire: bool, ) -> Result> { - let token_data = - decode_token::(token, ignore_expire).await?; - - //TODO: can this be validated alread in `decode_token`? - if token_data.claims.iss != APPLE_ISSUER { - return Err(Error::IssClaimMismatch); - } - - if token_data.claims.aud != client_id { - return Err(Error::ClientIdMismatch); - } - Ok(token_data) + let token_data = + decode_token::(token, ignore_expire).await?; + + //TODO: can this be validated alread in `decode_token`? + if token_data.claims.iss != APPLE_ISSUER { + return Err(Error::IssClaimMismatch); + } + + if token_data.claims.aud != client_id { + return Err(Error::ClientIdMismatch); + } + Ok(token_data) } /// allows to check whether the `validate` result was errored because of an expired signature #[must_use] pub fn is_expired( - validate_result: &Result>, + validate_result: &Result>, ) -> bool { - if let Err(Error::Jwt(error)) = validate_result { - return matches!( + if let Err(Error::Jwt(error)) = validate_result { + return matches!( error.kind(), jsonwebtoken::errors::ErrorKind::ExpiredSignature ); - } + } - false + false } #[cfg(test)] mod tests { - use crate::{ - decode_token, is_expired, validate, ClaimsServer2Server, - Error, - }; + use crate::{ + decode_token, is_expired, validate, ClaimsServer2Server, + Error, + }; #[tokio::test] async fn validate_test() -> std::result::Result<(), Error> { let user_token = "001026.16112b36378440d995af22b268f00984.1744"; - let client_id = "com.gameroasters.stack4"; + let client_id = "com.gameroasters.stack4"; let token = "eyJraWQiOiJZdXlYb1kiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwiYXVkIjoiY29tLmdhbWVyb2FzdGVycy5zdGFjazQiLCJleHAiOjE2MTQ1MTc1OTQsImlhdCI6MTYxNDQzMTE5NCwic3ViIjoiMDAxMDI2LjE2MTEyYjM2Mzc4NDQwZDk5NWFmMjJiMjY4ZjAwOTg0LjE3NDQiLCJjX2hhc2giOiJNNVVDdW5GdTFKNjdhdVE2LXEta093IiwiZW1haWwiOiJ6ZGZ1N2p0dXVzQHByaXZhdGVyZWxheS5hcHBsZWlkLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjoidHJ1ZSIsImlzX3ByaXZhdGVfZW1haWwiOiJ0cnVlIiwiYXV0aF90aW1lIjoxNjE0NDMxMTk0LCJub25jZV9zdXBwb3J0ZWQiOnRydWV9.GuMJfVbnEvqppwwHFZjn3GDJtB4c4rl7C4PZzyDsdyiuXcFcXq52Ti0WSJBsqtfyT2dXvYxVxebHtONSQha_9DiM5qfYTZbpDDlIXrOMy1fkfStocold_wHWavofIpoJQVUMj45HLHtjixiNE903Pho6eY2UjEUjB3aFe8txuFIMv2JsaMCYzG4-e632FKBn63SroCkLc-8b4EVV4iYqnC5AfZArXhVjUevhhlaBH0E8Az2OGEe74U2WgBvMXEilmd62Ek-uInnrpJRgYQfYXvehQ1yT3aMiIgJICTQFMDdL1KAvs6mc081lNJLFYvViWlMH-Y7E5ajtUiMApiNYsg"; - let result = - validate(client_id, token, true) - .await?; + let result = + validate(client_id, token, true) + .await?; assert_eq!(result.claims.sub, user_token); assert_eq!(result.claims.aud, client_id); - Ok(()) - } - - #[tokio::test] - async fn validate_no_email() { - let token = "eyJraWQiOiJlWGF1bm1MIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwiYXVkIjoiY29tLmdhbWVyb2FzdGVycy5zdGFjazQiLCJleHAiOjE2MzA4Mjc4MzAsImlhdCI6MTYzMDc0MTQzMCwic3ViIjoiMDAxMDI2LjE2MTEyYjM2Mzc4NDQwZDk5NWFmMjJiMjY4ZjAwOTg0LjE3NDQiLCJjX2hhc2giOiI0QjZKWTU4TmstVUJsY3dMa2VLc2lnIiwiYXV0aF90aW1lIjoxNjMwNzQxNDMwLCJub25jZV9zdXBwb3J0ZWQiOnRydWV9.iW0xk__fPD0mlh9UU-vh9VnR8yekWq64sl5re5d7UmDJxb1Fzk1Kca-hkA_Ka1LhSmKADdFW0DYEZhckqh49DgFtFdx6hM9t7guK3yrvBglhF5LAyb8NR028npxioLTTIgP_aR6Bpy5AyLQrU-yYEx2WTPYV5ln9n8vW154gZKRyl2KBlj9fS11BL_X1UFbFrL21GG_iPbB4qt5ywwTPoJ-diGN5JQzP5fk4yU4e4YmHhxJrT0NTTux2mB3lGJLa6YN-JYe_BuVV9J-sg_2r_ugTOUp3xQpfntu8xgQrY5W0oPxAPM4sibNLsye2kgPYYxfRYowc0JIjOcOd_JHDbQ"; - - validate( - "com.gameroasters.stack4", - token, - true, - ) - .await - .unwrap(); - } - - #[tokio::test] - async fn validate_expired() { - let token = "eyJraWQiOiJlWGF1bm1MIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwiYXVkIjoiY29tLmdhbWVyb2FzdGVycy5zdGFjazQiLCJleHAiOjE2MzA4Mjc4MzAsImlhdCI6MTYzMDc0MTQzMCwic3ViIjoiMDAxMDI2LjE2MTEyYjM2Mzc4NDQwZDk5NWFmMjJiMjY4ZjAwOTg0LjE3NDQiLCJjX2hhc2giOiI0QjZKWTU4TmstVUJsY3dMa2VLc2lnIiwiYXV0aF90aW1lIjoxNjMwNzQxNDMwLCJub25jZV9zdXBwb3J0ZWQiOnRydWV9.iW0xk__fPD0mlh9UU-vh9VnR8yekWq64sl5re5d7UmDJxb1Fzk1Kca-hkA_Ka1LhSmKADdFW0DYEZhckqh49DgFtFdx6hM9t7guK3yrvBglhF5LAyb8NR028npxioLTTIgP_aR6Bpy5AyLQrU-yYEx2WTPYV5ln9n8vW154gZKRyl2KBlj9fS11BL_X1UFbFrL21GG_iPbB4qt5ywwTPoJ-diGN5JQzP5fk4yU4e4YmHhxJrT0NTTux2mB3lGJLa6YN-JYe_BuVV9J-sg_2r_ugTOUp3xQpfntu8xgQrY5W0oPxAPM4sibNLsye2kgPYYxfRYowc0JIjOcOd_JHDbQ"; - - let res = validate( - "com.gameroasters.stack4", - token, - false, - ) - .await; - - assert!(is_expired(&res)); - } - - #[tokio::test] - async fn test_server_to_server_payload() { - let token = "eyJraWQiOiJlWGF1bm1MIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwiYXVkIjoiY29tLmdhbWVyb2FzdGVycy5zdGFjazQiLCJleHAiOjE2MzAxNzE4MTIsImlhdCI6MTYzMDA4NTQxMiwianRpIjoiQjk0T2REMDNwRnNhWWFOLUZ0djdtQSIsImV2ZW50cyI6IntcInR5cGVcIjpcImVtYWlsLWRpc2FibGVkXCIsXCJzdWJcIjpcIjAwMTAyNi4xNjExMmIzNjM3ODQ0MGQ5OTVhZjIyYjI2OGYwMDk4NC4xNzQ0XCIsXCJldmVudF90aW1lXCI6MTYzMDA4NTQwMzY0OCxcImVtYWlsXCI6XCJ6ZGZ1N2p0dXVzQHByaXZhdGVyZWxheS5hcHBsZWlkLmNvbVwiLFwiaXNfcHJpdmF0ZV9lbWFpbFwiOlwidHJ1ZVwifSJ9.SSdUM88GHqrS0QXHtaehbPxLQkAB3s1-qzcy3i2iRoSCzDhA1Q3o_FhiCbqOsbiPDOQ9aA1Z8-oAz1p3-TMfHy6QdIs1vLxBmNTe5IazNJw_7wwDZG2nr-bsKPUQldE--tK1EUFXQqQxQbfjJJE0JFEwPib2rmnb-t0mRopKMx2wg3CUlI64BHI2O8giGCbWB7UbJs2BpcUuapVShCIR7Eqxy0_ud81CUDjKzZK2CcmSRGDIk8g9pRqOHmPUFMOrDjj6_hUR9mf-xCrCedoC9f05z_yKD026A4gWGFn4pxTP8-uDTRPxcONax_vnQHBUDigYi8HXuzWorTx2ORPjaw"; - - let result = decode_token::( - token, - true, - ) - .await - .unwrap(); - - assert_eq!(result.claims.aud, "com.gameroasters.stack4"); - assert_eq!( - result.claims.events.sub, - "001026.16112b36378440d995af22b268f00984.1744" - ); - - println!("{:?}", result); - } + Ok(()) + } + + #[tokio::test] + async fn validate_no_email() { + let token = "eyJraWQiOiJlWGF1bm1MIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwiYXVkIjoiY29tLmdhbWVyb2FzdGVycy5zdGFjazQiLCJleHAiOjE2MzA4Mjc4MzAsImlhdCI6MTYzMDc0MTQzMCwic3ViIjoiMDAxMDI2LjE2MTEyYjM2Mzc4NDQwZDk5NWFmMjJiMjY4ZjAwOTg0LjE3NDQiLCJjX2hhc2giOiI0QjZKWTU4TmstVUJsY3dMa2VLc2lnIiwiYXV0aF90aW1lIjoxNjMwNzQxNDMwLCJub25jZV9zdXBwb3J0ZWQiOnRydWV9.iW0xk__fPD0mlh9UU-vh9VnR8yekWq64sl5re5d7UmDJxb1Fzk1Kca-hkA_Ka1LhSmKADdFW0DYEZhckqh49DgFtFdx6hM9t7guK3yrvBglhF5LAyb8NR028npxioLTTIgP_aR6Bpy5AyLQrU-yYEx2WTPYV5ln9n8vW154gZKRyl2KBlj9fS11BL_X1UFbFrL21GG_iPbB4qt5ywwTPoJ-diGN5JQzP5fk4yU4e4YmHhxJrT0NTTux2mB3lGJLa6YN-JYe_BuVV9J-sg_2r_ugTOUp3xQpfntu8xgQrY5W0oPxAPM4sibNLsye2kgPYYxfRYowc0JIjOcOd_JHDbQ"; + + validate( + "com.gameroasters.stack4", + token, + true, + ) + .await + .unwrap(); + } + + #[tokio::test] + async fn validate_expired() { + let token = "eyJraWQiOiJlWGF1bm1MIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwiYXVkIjoiY29tLmdhbWVyb2FzdGVycy5zdGFjazQiLCJleHAiOjE2MzA4Mjc4MzAsImlhdCI6MTYzMDc0MTQzMCwic3ViIjoiMDAxMDI2LjE2MTEyYjM2Mzc4NDQwZDk5NWFmMjJiMjY4ZjAwOTg0LjE3NDQiLCJjX2hhc2giOiI0QjZKWTU4TmstVUJsY3dMa2VLc2lnIiwiYXV0aF90aW1lIjoxNjMwNzQxNDMwLCJub25jZV9zdXBwb3J0ZWQiOnRydWV9.iW0xk__fPD0mlh9UU-vh9VnR8yekWq64sl5re5d7UmDJxb1Fzk1Kca-hkA_Ka1LhSmKADdFW0DYEZhckqh49DgFtFdx6hM9t7guK3yrvBglhF5LAyb8NR028npxioLTTIgP_aR6Bpy5AyLQrU-yYEx2WTPYV5ln9n8vW154gZKRyl2KBlj9fS11BL_X1UFbFrL21GG_iPbB4qt5ywwTPoJ-diGN5JQzP5fk4yU4e4YmHhxJrT0NTTux2mB3lGJLa6YN-JYe_BuVV9J-sg_2r_ugTOUp3xQpfntu8xgQrY5W0oPxAPM4sibNLsye2kgPYYxfRYowc0JIjOcOd_JHDbQ"; + + let res = validate( + "com.gameroasters.stack4", + token, + false, + ) + .await; + + assert!(is_expired(&res)); + } + + #[tokio::test] + async fn test_server_to_server_payload() { + let token = "eyJraWQiOiJlWGF1bm1MIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwiYXVkIjoiY29tLmdhbWVyb2FzdGVycy5zdGFjazQiLCJleHAiOjE2MzAxNzE4MTIsImlhdCI6MTYzMDA4NTQxMiwianRpIjoiQjk0T2REMDNwRnNhWWFOLUZ0djdtQSIsImV2ZW50cyI6IntcInR5cGVcIjpcImVtYWlsLWRpc2FibGVkXCIsXCJzdWJcIjpcIjAwMTAyNi4xNjExMmIzNjM3ODQ0MGQ5OTVhZjIyYjI2OGYwMDk4NC4xNzQ0XCIsXCJldmVudF90aW1lXCI6MTYzMDA4NTQwMzY0OCxcImVtYWlsXCI6XCJ6ZGZ1N2p0dXVzQHByaXZhdGVyZWxheS5hcHBsZWlkLmNvbVwiLFwiaXNfcHJpdmF0ZV9lbWFpbFwiOlwidHJ1ZVwifSJ9.SSdUM88GHqrS0QXHtaehbPxLQkAB3s1-qzcy3i2iRoSCzDhA1Q3o_FhiCbqOsbiPDOQ9aA1Z8-oAz1p3-TMfHy6QdIs1vLxBmNTe5IazNJw_7wwDZG2nr-bsKPUQldE--tK1EUFXQqQxQbfjJJE0JFEwPib2rmnb-t0mRopKMx2wg3CUlI64BHI2O8giGCbWB7UbJs2BpcUuapVShCIR7Eqxy0_ud81CUDjKzZK2CcmSRGDIk8g9pRqOHmPUFMOrDjj6_hUR9mf-xCrCedoC9f05z_yKD026A4gWGFn4pxTP8-uDTRPxcONax_vnQHBUDigYi8HXuzWorTx2ORPjaw"; + + let result = decode_token::( + token, + true, + ) + .await + .unwrap(); + + assert_eq!(result.claims.aud, "com.gameroasters.stack4"); + assert_eq!( + result.claims.events.sub, + "001026.16112b36378440d995af22b268f00984.1744" + ); + + println!("{:?}", result); + } }