use base64::Engine;
use jwt_verify::{CognitoJwtVerifier, JwtError, JwtVerifier, VerifierConfig};
use std::time::{SystemTime, UNIX_EPOCH};
fn create_test_token(header: &str, payload: &str, signature: &str) -> String {
format!("{}.{}.{}", header, payload, signature)
}
fn base64_encode(data: &str) -> String {
base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(data)
}
fn current_timestamp() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs()
}
fn create_test_id_token(issuer: &str, kid: &str, exp_offset: i64) -> String {
let now = current_timestamp();
let exp = now + exp_offset as u64;
let header = base64_encode(&format!(r#"{{"alg":"RS256","kid":"{}","typ":"JWT"}}"#, kid));
let payload = base64_encode(&format!(
r#"{{
"sub": "test-user-id",
"iss": "{}",
"client_id": "test-client-id",
"token_use": "id",
"auth_time": {},
"exp": {},
"iat": {},
"jti": "test-jwt-id",
"email": "test@example.com",
"email_verified": true,
"cognito:username": "testuser",
"aud": "test-client-id"
}}"#,
issuer,
now - 300,
exp,
now
));
let signature = "test-signature";
create_test_token(&header, &payload, signature)
}
fn create_test_access_token(issuer: &str, kid: &str, exp_offset: i64) -> String {
let now = current_timestamp();
let exp = now + exp_offset as u64;
let header = base64_encode(&format!(r#"{{"alg":"RS256","kid":"{}","typ":"JWT"}}"#, kid));
let payload = base64_encode(&format!(
r#"{{
"sub": "test-user-id",
"iss": "{}",
"client_id": "test-client-id",
"token_use": "access",
"auth_time": {},
"exp": {},
"iat": {},
"jti": "test-jwt-id",
"scope": "openid profile email",
"version": 2
}}"#,
issuer,
now - 300,
exp,
now
));
let signature = "test-signature";
create_test_token(&header, &payload, signature)
}
fn create_test_refresh_token(issuer: &str, kid: &str) -> String {
let now = current_timestamp();
let exp = now + 3600;
let header = base64_encode(&format!(r#"{{"alg":"RS256","kid":"{}","typ":"JWT"}}"#, kid));
let payload = base64_encode(&format!(
r#"{{
"sub": "test-user-id",
"iss": "{}",
"client_id": "test-client-id",
"token_use": "refresh",
"auth_time": {},
"exp": {},
"iat": {},
"jti": "test-jwt-id"
}}"#,
issuer,
now - 300,
exp,
now
));
let signature = "test-signature";
create_test_token(&header, &payload, signature)
}
fn create_test_token_with_invalid_signature(issuer: &str, kid: &str) -> String {
let now = current_timestamp();
let exp = now + 3600;
let header = base64_encode(&format!(r#"{{"alg":"RS256","kid":"{}","typ":"JWT"}}"#, kid));
let payload = base64_encode(&format!(
r#"{{
"sub": "test-user-id",
"iss": "{}",
"client_id": "test-client-id",
"token_use": "id",
"auth_time": {},
"exp": {},
"iat": {},
"jti": "test-jwt-id"
}}"#,
issuer,
now - 300,
exp,
now
));
let signature = "tampered-signature";
create_test_token(&header, &payload, signature)
}
fn create_test_token_with_invalid_issuer(kid: &str) -> String {
let now = current_timestamp();
let exp = now + 3600;
let header = base64_encode(&format!(r#"{{"alg":"RS256","kid":"{}","typ":"JWT"}}"#, kid));
let payload = base64_encode(&format!(
r#"{{
"sub": "test-user-id",
"iss": "https://invalid-issuer.com",
"client_id": "test-client-id",
"token_use": "id",
"auth_time": {},
"exp": {},
"iat": {},
"jti": "test-jwt-id"
}}"#,
now - 300,
exp,
now
));
let signature = "test-signature";
create_test_token(&header, &payload, signature)
}
fn create_test_token_with_invalid_client_id(issuer: &str, kid: &str) -> String {
let now = current_timestamp();
let exp = now + 3600;
let header = base64_encode(&format!(r#"{{"alg":"RS256","kid":"{}","typ":"JWT"}}"#, kid));
let payload = base64_encode(&format!(
r#"{{
"sub": "test-user-id",
"iss": "{}",
"client_id": "invalid-client-id",
"token_use": "id",
"auth_time": {},
"exp": {},
"iat": {},
"jti": "test-jwt-id"
}}"#,
issuer,
now - 300,
exp,
now
));
let signature = "test-signature";
create_test_token(&header, &payload, signature)
}
fn create_test_expired_token(issuer: &str, kid: &str) -> String {
let now = current_timestamp();
let exp = now - 3600;
let header = base64_encode(&format!(r#"{{"alg":"RS256","kid":"{}","typ":"JWT"}}"#, kid));
let payload = base64_encode(&format!(
r#"{{
"sub": "test-user-id",
"iss": "{}",
"client_id": "test-client-id",
"token_use": "id",
"auth_time": {},
"exp": {},
"iat": {},
"jti": "test-jwt-id"
}}"#,
issuer,
now - 7200,
exp,
now - 7200
));
let signature = "test-signature";
create_test_token(&header, &payload, signature)
}
#[tokio::test]
async fn test_missing_token() {
let config = VerifierConfig::new(
"us-east-1",
"us-east-1_example",
&["test-client-id".to_string()],
None,
)
.unwrap();
let verifier = CognitoJwtVerifier::new(vec![config]).unwrap();
let result = verifier.verify_id_token("").await;
assert!(result.is_err());
match result.unwrap_err() {
JwtError::MissingToken => {
}
err => panic!("Expected MissingToken error, got: {:?}", err),
}
}
#[tokio::test]
async fn test_invalid_token_format() {
let config = VerifierConfig::new(
"us-east-1",
"us-east-1_example",
&["test-client-id".to_string()],
None,
)
.unwrap();
let verifier = CognitoJwtVerifier::new(vec![config]).unwrap();
let result = verifier.verify_id_token("invalid.token").await;
assert!(result.is_err());
match result.unwrap_err() {
JwtError::ParseError { .. } => {
}
err => panic!("Expected ParseError, got: {:?}", err),
}
}
#[tokio::test]
async fn test_invalid_issuer() {
let config = VerifierConfig::new(
"us-east-1",
"us-east-1_example",
&["test-client-id".to_string()],
None,
)
.unwrap();
let verifier = CognitoJwtVerifier::new(vec![config]).unwrap();
let token = create_test_token_with_invalid_issuer("test-key-1");
let result = verifier.verify_id_token(&token).await;
assert!(result.is_err());
match result.unwrap_err() {
JwtError::ConfigurationError { parameter, .. } => {
assert_eq!(parameter, Some("issuer".to_string()));
}
err => panic!("Expected ConfigurationError, got: {:?}", err),
}
}
#[tokio::test]
async fn test_different_token_types() {
let config = VerifierConfig::new(
"us-east-1",
"us-east-1_example",
&["test-client-id".to_string()],
None,
)
.unwrap();
let verifier = CognitoJwtVerifier::new(vec![config]).unwrap();
let issuer = "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_example";
let id_token = create_test_id_token(issuer, "test-key-1", 3600);
let access_token = create_test_access_token(issuer, "test-key-1", 3600);
let refresh_token = create_test_refresh_token(issuer, "test-key-1");
let id_result = verifier.verify_id_token(&id_token).await;
assert!(id_result.is_err());
let access_result = verifier.verify_access_token(&access_token).await;
assert!(access_result.is_err());
let refresh_result = verifier.verify_id_token(&refresh_token).await;
assert!(refresh_result.is_err());
}
#[tokio::test]
async fn test_multiple_user_pools() {
let config1 = VerifierConfig::new(
"us-east-1",
"us-east-1_example1",
&["test-client-id-1".to_string()],
None,
)
.unwrap();
let config2 = VerifierConfig::new(
"us-west-2",
"us-west-2_example2",
&["test-client-id-2".to_string()],
None,
)
.unwrap();
let verifier = CognitoJwtVerifier::new(vec![config1, config2]).unwrap();
let issuer1 = "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_example1";
let issuer2 = "https://cognito-idp.us-west-2.amazonaws.com/us-west-2_example2";
let token1 = create_test_id_token(issuer1, "test-key-1", 3600);
let token2 = create_test_id_token(issuer2, "test-key-1", 3600);
let result1 = verifier.verify_id_token(&token1).await;
let result2 = verifier.verify_id_token(&token2).await;
assert!(result1.is_err());
assert!(result2.is_err());
}
#[tokio::test]
async fn test_add_and_remove_user_pool() {
let mut verifier = CognitoJwtVerifier::new(vec![]).unwrap();
let pool_ids = verifier.get_user_pool_ids();
assert_eq!(pool_ids.len(), 0);
let config = VerifierConfig::new(
"us-east-1",
"us-east-1_example",
&["test-client-id".to_string()],
None,
).unwrap();
let result = verifier.add_user_pool("test-pool", config);
assert!(result.is_ok());
let pool_ids = verifier.get_user_pool_ids();
assert_eq!(pool_ids.len(), 1);
assert!(pool_ids.contains(&"test-pool".to_string()));
let result = verifier.add_user_pool_with_params(
"test-pool-2",
"us-west-2",
"us-west-2_example",
&["test-client-id-2".to_string()],
);
assert!(result.is_ok());
let pool_ids = verifier.get_user_pool_ids();
assert_eq!(pool_ids.len(), 2);
assert!(pool_ids.contains(&"test-pool".to_string()));
assert!(pool_ids.contains(&"test-pool-2".to_string()));
let result = verifier.remove_user_pool("test-pool");
assert!(result.is_ok());
let pool_ids = verifier.get_user_pool_ids();
assert_eq!(pool_ids.len(), 1);
assert!(!pool_ids.contains(&"test-pool".to_string()));
assert!(pool_ids.contains(&"test-pool-2".to_string()));
let result = verifier.remove_user_pool("non-existent-pool");
assert!(result.is_err());
match result.unwrap_err() {
JwtError::ConfigurationError { parameter, .. } => {
assert_eq!(parameter, Some("pool_id".to_string()));
},
err => panic!("Expected ConfigurationError, got: {:?}", err),
}
}
#[tokio::test]
async fn test_new_single_pool() {
let verifier = CognitoJwtVerifier::new_single_pool(
"us-east-1",
"us-east-1_example",
&["test-client-id".to_string()],
).unwrap();
let pool_ids = verifier.get_user_pool_ids();
assert_eq!(pool_ids.len(), 1);
assert!(pool_ids.contains(&"us-east-1_us-east-1_example".to_string()));
}
#[tokio::test]
async fn test_prefetch_all() {
let config1 = VerifierConfig::new(
"us-east-1",
"us-east-1_example1",
&["test-client-id-1".to_string()],
None,
).unwrap();
let config2 = VerifierConfig::new(
"us-west-2",
"us-west-2_example2",
&["test-client-id-2".to_string()],
None,
).unwrap();
let verifier = CognitoJwtVerifier::new(vec![config1, config2]).unwrap();
let results = verifier.hydrate().await;
assert_eq!(results.len(), 2);
let pool_ids: Vec<String> = results.iter().map(|(id, _)| id.clone()).collect();
assert!(pool_ids.contains(&"us-east-1_us-east-1_example1".to_string()));
assert!(pool_ids.contains(&"us-west-2_us-west-2_example2".to_string()));
}
#[tokio::test]
async fn test_invalid_token_use() {
let config = VerifierConfig::new(
"us-east-1",
"us-east-1_example",
&["test-client-id".to_string()],
None,
).unwrap();
let verifier = CognitoJwtVerifier::new(vec![config]).unwrap();
let issuer = "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_example";
let access_token = create_test_access_token(issuer, "test-key-1", 3600);
let result = verifier.verify_access_token(&access_token).await;
assert!(result.is_err());
let config = VerifierConfig::new(
"us-east-1",
"us-east-1_example",
&["test-client-id".to_string()],
None,
).unwrap();
let verifier = CognitoJwtVerifier::new(vec![config]).unwrap();
let id_token = create_test_id_token(issuer, "test-key-1", 3600);
let result = verifier.verify_id_token(&id_token).await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_refresh_token_rejection() {
let config = VerifierConfig::new(
"us-east-1",
"us-east-1_example",
&["test-client-id".to_string()],
None,
).unwrap();
let verifier = CognitoJwtVerifier::new(vec![config]).unwrap();
let issuer = "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_example";
let refresh_token = create_test_refresh_token(issuer, "test-key-1");
let result = verifier.verify_id_token(&refresh_token).await;
assert!(result.is_err());
match result.unwrap_err() {
JwtError::ConfigurationError { .. } => {
},
err => println!("Got error: {:?}", err),
}
}
#[tokio::test]
async fn test_invalid_signature_token() {
let config = VerifierConfig::new(
"us-east-1",
"us-east-1_example",
&["test-client-id".to_string()],
None,
).unwrap();
let verifier = CognitoJwtVerifier::new(vec![config]).unwrap();
let issuer = "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_example";
let token = create_test_token_with_invalid_signature(issuer, "test-key-1");
let result = verifier.verify_id_token(&token).await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_expired_token() {
let config = VerifierConfig::new(
"us-east-1",
"us-east-1_example",
&["test-client-id".to_string()],
None,
).unwrap();
let verifier = CognitoJwtVerifier::new(vec![config]).unwrap();
let issuer = "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_example";
let token = create_test_expired_token(issuer, "test-key-1");
let result = verifier.verify_id_token(&token).await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_invalid_client_id() {
let config = VerifierConfig::new(
"us-east-1",
"us-east-1_example",
&["valid-client-id".to_string()],
None,
).unwrap();
let verifier = CognitoJwtVerifier::new(vec![config]).unwrap();
let issuer = "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_example";
let token = create_test_token_with_invalid_client_id(issuer, "test-key-1");
let result = verifier.verify_id_token(&token).await;
assert!(result.is_err());
}