Skip to content

Commit

Permalink
test: Add token verify tests
Browse files Browse the repository at this point in the history
  • Loading branch information
mzaniolo committed Nov 26, 2024
1 parent 095afea commit 418dea0
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 4 deletions.
3 changes: 2 additions & 1 deletion src/v2/authentication.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,8 @@ impl Token {
Ok(())
}

pub fn is_expired(&self) -> bool {
/// Check if the token is expired
pub(crate) fn is_expired(&self) -> bool {
OffsetDateTime::now_utc() > self.expiry
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/v2/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
#![allow(clippy::missing_docs_in_private_items)]
//! Communication with Zitadel using http [v2 API](https://zitadel.com/docs/apis/v2)
mod authentication;
/// Service user authentication
pub mod authentication;
/// Helper client to authenticate tokens
pub mod token;
pub mod users;
Expand Down
109 changes: 107 additions & 2 deletions tests/e2e.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,19 @@
clippy::unwrap_used
)]

use std::path::Path;
use std::{collections::HashMap, path::Path};

use anyhow::{bail, Result};
use futures::StreamExt;
use josekit::{jws::JwsHeader, jwt::JwtPayload};
use test_log::{self, test};
use time::{Duration, OffsetDateTime};
use url::Url;
use zitadel_rust_client::v2::{users::*, Zitadel};
use wiremock::{
matchers::{method, path},
Mock, MockServer, ResponseTemplate,
};
use zitadel_rust_client::v2::{authentication::Token, token, users::*, Zitadel};

const USER_SERVICE_PATH: &str = "tests/environment/zitadel/service-user.json";

Expand Down Expand Up @@ -387,3 +393,102 @@ async fn test_e2e_user_metadata_bulk() -> Result<()> {

Ok(())
}

#[tokio::test]
async fn test_e2e_simple_token_verification() -> Result<()> {
let url = Url::parse("http://localhost:8080")?;
let service_account_file = Path::new(USER_SERVICE_PATH).to_path_buf();
let client = reqwest::Client::new();

// Work around to get set the token type to JWT for the service account
// TODO: Remove it once we have the zitadel bootstrap in place
let mut put_url = url.clone();
let service_account: HashMap<String, serde_json::Value> =
serde_json::from_str(std::fs::read_to_string(&service_account_file)?.as_ref())?;
put_url.set_path(&format!(
"management/v1/users/{}/machine",
service_account.get("userId").and_then(|v| v.as_str()).unwrap()
));
let token = Token::new(&url, &service_account_file, client.clone()).await?.token;
let r = client
.put(put_url)
.bearer_auth(token)
.body(r#"{"accessTokenType": "ACCESS_TOKEN_TYPE_JWT", "name":"Admin"}"#)
.send()
.await?;
println!("body:\n{}", r.text().await?);

let token = Token::new(&url, &service_account_file, client).await?.token;
let token_verifier = token::ZitadelJWTVerifier::new(url);

assert!(token_verifier.verify(token).await.is_ok());

Ok(())
}

#[tokio::test]
async fn test_e2e_token_verification_negative() -> Result<()> {
let mock = MockServer::start().await;
let mut jwks = josekit::jwk::JwkSet::from_bytes(r#"{"keys":[]}"#)?;
let mut private_key = josekit::jwk::Jwk::generate_rsa_key(2048)?;
let key_id = "123456";
private_key.set_key_id(key_id);
jwks.push_key(private_key.to_public_key()?);
let signer = josekit::jws::RS256.signer_from_jwk(&private_key)?;

let mut header = JwsHeader::new();
header.set_key_id(key_id);
header.set_token_type("JWT");
header.set_algorithm("RS256");

Mock::given(method("GET"))
.and(path("/oauth/v2/keys"))
.respond_with(
ResponseTemplate::new(200)
.set_body_string(jwks.to_string())
.insert_header("Cache-control", "max-age=60"),
)
.mount(&mock)
.await;

let url = Url::parse(&mock.uri())?;
let token_verifier = token::ZitadelJWTVerifier::new(url);

let now = OffsetDateTime::now_utc();

let mut base_payload = JwtPayload::new();
base_payload.set_issuer(mock.uri());
base_payload.set_expires_at(&(now + Duration::minutes(10)).into());
base_payload.set_not_before(&(now - Duration::minutes(10)).into());
base_payload.set_claim("test_claim", Some("test_value".into()))?;

let mut jwts: Vec<(&JwtPayload, &JwsHeader)> = Vec::new();

let mut header_wrong_kid = header.clone();
header_wrong_kid.set_key_id("987654");
jwts.push((&base_payload, &header_wrong_kid));

let mut payload_expired = base_payload.clone();
payload_expired.set_expires_at(&(now - Duration::minutes(5)).into());
jwts.push((&payload_expired, &header));

let mut payload_before = base_payload.clone();
payload_before.set_not_before(&(now + Duration::minutes(1)).into());
jwts.push((&payload_before, &header));

let mut payload_wrong_issuer = base_payload.clone();
payload_wrong_issuer.set_issuer("Wrong_issuer");
jwts.push((&payload_wrong_issuer, &header));

for (payload, header) in jwts {
let jwt = josekit::jwt::encode_with_signer(payload, header, &signer)?;
assert!(token_verifier.verify(jwt).await.is_err());
}

let wrong_private_key = josekit::jwk::Jwk::generate_rsa_key(2048)?;
let wrong_signer = josekit::jws::RS256.signer_from_jwk(&wrong_private_key)?;
let jwt = josekit::jwt::encode_with_signer(&base_payload, &header, &wrong_signer)?;
assert!(token_verifier.verify(jwt).await.is_err());

Ok(())
}

0 comments on commit 418dea0

Please sign in to comment.