From 85d5bf7049a0e547b340d5703c175dc7cbbb252e Mon Sep 17 00:00:00 2001 From: ralvescosta Date: Fri, 26 Apr 2024 09:02:09 -0300 Subject: [PATCH] feat: auth unit test --- Cargo.lock | 8 ++ README.md | 2 - auth/Cargo.toml | 6 +- auth/src/manager.rs | 206 +++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 216 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a8afbf4..80ec5d5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -563,9 +563,11 @@ name = "auth" version = "0.1.0" dependencies = [ "async-trait", + "base64 0.13.1", "configs", "jsonwebtoken", "moka", + "openssl", "opentelemetry", "reqwest", "serde", @@ -990,6 +992,12 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + [[package]] name = "base64" version = "0.21.7" diff --git a/README.md b/README.md index d847638..eba2820 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,5 @@ # Ruskit -:warning::construction: **Work In Progress** :construction::warning: - [![ci](https://github.com/ralvescosta/ruskit/actions/workflows/ci.yml/badge.svg)](https://github.com/ralvescosta/ruskit/actions/workflows/ci.yml) [![codecov](https://codecov.io/gh/ralvescosta/ruskit/branch/main/graph/badge.svg?token=6EAILKZFDO)](https://codecov.io/gh/ralvescosta/ruskit) [![GitHub License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/ralvescosta/ruskit/blob/main/LICENSE) diff --git a/auth/Cargo.toml b/auth/Cargo.toml index 290200c..b20bcfe 100644 --- a/auth/Cargo.toml +++ b/auth/Cargo.toml @@ -15,4 +15,8 @@ thiserror = { workspace = true } tokio = { workspace = true, features = ["sync"] } reqwest = { version = "0.12.3", features = ["json"] } moka = { version = "0.12.7", features = ["future"] } -jsonwebtoken = { version = "9.3.0" } \ No newline at end of file +jsonwebtoken = { version = "9.3.0" } + +[dev-dependencies] +openssl = "0.10" +base64 = "0.13" \ No newline at end of file diff --git a/auth/src/manager.rs b/auth/src/manager.rs index a51c067..5cc42a5 100644 --- a/auth/src/manager.rs +++ b/auth/src/manager.rs @@ -34,9 +34,9 @@ pub trait JwtManager: Send + Sync { }; let Some(jwk) = jwks.find(&kid) else { - error!("wasn't possible to find the same token kid into jwks"); + error!("wasn't possible to find the some token kid into jwks"); return Err(AuthError::InvalidToken( - "wasn't possible to find the same token kid into jwks".into(), + "wasn't possible to find the some token kid into jwks".into(), )); }; @@ -68,7 +68,6 @@ pub trait JwtManager: Send + Sync { let mut validation = Validation::new(alg); - //Validation::SubjectPresent validation.set_audience(&[aud]); validation.set_issuer(&[iss]); validation.validate_exp = true; @@ -82,3 +81,204 @@ pub trait JwtManager: Send + Sync { } } } + +#[cfg(test)] +mod tests { + use super::*; + use jsonwebtoken::{encode, EncodingKey, Header}; + use openssl::rsa::Rsa; + use serde::{Deserialize, Serialize}; + use serde_json::json; + + #[derive(Debug, Serialize, Deserialize)] + struct MockedClaims { + sub: String, + company: String, + exp: usize, + } + + impl MockedClaims { + pub fn new() -> Self { + Self { + sub: "1234567890".to_owned(), + company: "ACME".to_owned(), + exp: 10000000000, + } + } + } + + struct MyJwtManager; + #[async_trait] + impl JwtManager for MyJwtManager { + async fn verify(&self, _ctx: &Context, _token: &str) -> Result { + todo!() + } + } + + struct SetupTests { + pub manager: Box, + pub jwk_set: JwkSet, + pub claims: MockedClaims, + pub token: String, + } + + impl SetupTests { + pub fn new(without_kid: bool, wrong_algorithm: bool) -> Self { + let claims = MockedClaims::new(); + + let rsa = Rsa::generate(2048).unwrap(); + let private_key_pem = rsa.private_key_to_pem().unwrap(); + let _public_key_pem = rsa.public_key_to_pem().unwrap(); + + let n = base64::encode_config(rsa.n().to_vec(), base64::URL_SAFE_NO_PAD); + let e = base64::encode_config(rsa.e().to_vec(), base64::URL_SAFE_NO_PAD); + + // Construct the JWK + let serialized = json!({ + "keys": [{ + "kty": "RSA", + "n": n, + "e": e, + "use": "sig", + "alg": "RS256", + "kid": "key1" + }] + }); + + let jwk_set: JwkSet = serde_json::from_value(serialized).unwrap(); + + let encoding_key = EncodingKey::from_rsa_pem(&private_key_pem).unwrap(); + let token = encode( + &Header { + typ: Some("JWT".into()), + alg: Self::alg(wrong_algorithm), + cty: None, + jku: None, + jwk: Some(jwk_set.keys[0].clone()), + kid: Self::kid(without_kid), + x5u: None, + x5c: None, + x5t: None, + x5t_s256: None, + }, + &claims, + &encoding_key, + ) + .unwrap(); + + let manager = Box::new(MyJwtManager); + + Self { + manager, + jwk_set, + claims, + token, + } + } + + fn kid(without: bool) -> Option { + if without { + return None; + } + + return Some("key1".into()); + } + + fn alg(wrong: bool) -> jsonwebtoken::Algorithm { + if wrong { + return jsonwebtoken::Algorithm::RS512; + } + + return jsonwebtoken::Algorithm::RS256; + } + } + + #[test] + fn test_decode_token() { + let sut = SetupTests::new(false, false); + + let aud = "aud"; + let iss = "iss"; + + let token_data = sut.manager.decode_token(&sut.token, &sut.jwk_set, aud, iss); + assert!(token_data.is_ok()); + let token_data = token_data.unwrap(); + + let sub = token_data.claims.get("sub"); + assert!(sub.is_some()); + let sub = sub.unwrap().as_str().unwrap().to_string(); + + let company = token_data.claims.get("company"); + assert!(company.is_some()); + let company = company.unwrap().as_str().unwrap().to_string(); + + let exp = token_data.claims.get("exp"); + assert!(exp.is_some()); + let exp = exp.unwrap().as_i64().unwrap() as usize; + + assert_eq!(sub, sut.claims.sub); + assert_eq!(company, sut.claims.company); + assert_eq!(exp, sut.claims.exp); + } + + #[test] + fn test_decode_token_with_invalid_token() { + let sut = SetupTests::new(false, false); + + let token_data = + sut.manager + .decode_token("invalid token", &sut.jwk_set, "aud".into(), "iss".into()); + + assert!(token_data.is_err()); + assert_eq!( + token_data.unwrap_err(), + AuthError::InvalidToken("failed to decoded token header".into()) + ); + } + + #[test] + fn test_decode_token_with_invalid_header() { + let token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9."; + let sut = SetupTests::new(false, false); + + let token_data = sut + .manager + .decode_token(token, &sut.jwk_set, "aud".into(), "iss".into()); + + assert!(token_data.is_err()); + assert_eq!( + token_data.unwrap_err(), + AuthError::InvalidToken("failed to decoded token header".into()) + ); + } + + #[test] + fn test_decode_token_with_invalid_kid() { + let sut = SetupTests::new(true, false); + + let token_data = + sut.manager + .decode_token(&sut.token, &sut.jwk_set, "aud".into(), "iss".into()); + + assert!(token_data.is_err()); + assert_eq!( + token_data.unwrap_err(), + AuthError::InvalidToken("token header without kid".into()) + ); + } + + #[test] + fn test_decode_token_with_wrong_algorithm() { + let sut = SetupTests::new(false, true); + + let token_data = + sut.manager + .decode_token(&sut.token, &sut.jwk_set, "aud".into(), "iss".into()); + + assert!(token_data.is_err()); + assert_eq!( + token_data.unwrap_err(), + AuthError::InvalidToken("token validation error".into()) + ); + } +}