jwt-verify 0.1.0

JWT verification library for AWS Cognito tokens and any OIDC-compatible IDP
Documentation
#[cfg(test)]
mod tests {
    use crate::common::error::JwtError;
    use crate::common::token::TokenParser;
    use base64::Engine;
    use jsonwebtoken::{Algorithm, DecodingKey, Validation};

    // Helper function to create a test token
    fn create_test_token(header: &str, payload: &str, signature: &str) -> String {
        format!("{}.{}.{}", header, payload, signature)
    }

    // Helper function to create a base64 encoded string
    fn base64_encode(data: &str) -> String {
        base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(data)
    }

    #[test]
    fn test_parse_token_header_valid() {
        // Create a valid token header
        let header = base64_encode(r#"{"alg":"RS256","kid":"test-key-id","typ":"JWT"}"#);
        let token = create_test_token(&header, "payload", "signature");

        let result = TokenParser::parse_token_header(&token);
        assert!(result.is_ok());

        let header = result.unwrap();
        assert_eq!(header.alg, "RS256");
        assert_eq!(header.kid, "test-key-id");
        assert_eq!(header.token_type, Some("JWT".to_string()));
    }

    #[test]
    fn test_parse_token_header_empty_token() {
        let result = TokenParser::parse_token_header("");
        assert!(result.is_err());
        assert!(matches!(result.unwrap_err(), JwtError::MissingToken));
    }

    #[test]
    fn test_parse_token_header_invalid_format() {
        // Token with only one part
        let result = TokenParser::parse_token_header("header");
        assert!(result.is_err());
        assert!(matches!(result.unwrap_err(), JwtError::ParseError { .. }));

        // Token with two parts
        let result = TokenParser::parse_token_header("header.payload");
        assert!(result.is_err());
        assert!(matches!(result.unwrap_err(), JwtError::ParseError { .. }));
    }

    #[test]
    fn test_parse_token_header_invalid_base64() {
        // Invalid base64 in header
        let token = create_test_token("invalid!base64", "payload", "signature");
        let result = TokenParser::parse_token_header(&token);
        assert!(result.is_err());
        assert!(matches!(result.unwrap_err(), JwtError::ParseError { .. }));
    }

    #[test]
    fn test_parse_token_header_invalid_json() {
        // Invalid JSON in header
        let header = base64_encode("not json");
        let token = create_test_token(&header, "payload", "signature");
        let result = TokenParser::parse_token_header(&token);
        assert!(result.is_err());
        assert!(matches!(result.unwrap_err(), JwtError::ParseError { .. }));
    }

    #[test]
    fn test_parse_token_header_missing_kid() {
        // Header without kid
        let header = base64_encode(r#"{"alg":"RS256"}"#);
        let token = create_test_token(&header, "payload", "signature");
        let result = TokenParser::parse_token_header(&token);
        assert!(result.is_err());
        assert!(matches!(result.unwrap_err(), JwtError::ParseError { .. }));
    }

    #[test]
    fn test_parse_token_header_unsupported_algorithm() {
        // Header with unsupported algorithm
        let header = base64_encode(r#"{"alg":"HS256","kid":"test-key-id"}"#);
        let token = create_test_token(&header, "payload", "signature");
        let result = TokenParser::parse_token_header(&token);
        assert!(result.is_err());
        assert!(matches!(result.unwrap_err(), JwtError::InvalidClaim { .. }));
    }

    #[test]
    fn test_parse_token_claims_valid() {
        // Create a test token with valid claims
        let header = base64_encode(r#"{"alg":"RS256","kid":"test-key-id"}"#);
        let payload = base64_encode(
            r#"{"sub":"user123","iss":"https://example.com","exp":4070908800,"iat":1609459200,"client_id":"client123","token_use":"id"}"#,
        );
        let token = create_test_token(&header, &payload, "signature");

        // Create a test key and validation
        let key = DecodingKey::from_secret(b"test-secret");
        let mut validation = Validation::new(Algorithm::RS256);
        validation.validate_exp = false; // Disable expiration validation for this test
        validation.insecure_disable_signature_validation(); // Disable signature validation for this test

        // Define a struct to deserialize the claims
        #[derive(serde::Deserialize, Debug)]
        struct TestClaims {
            sub: String,
            iss: String,
            exp: u64,
            iat: u64,
            client_id: String,
            token_use: String,
        }

        let result = TokenParser::parse_token_claims::<TestClaims>(&token, &key, &validation);
        assert!(result.is_ok());

        let claims = result.unwrap();
        assert_eq!(claims.sub, "user123");
        assert_eq!(claims.iss, "https://example.com");
        assert_eq!(claims.exp, 4070908800); // 2099-01-01
        assert_eq!(claims.iat, 1609459200); // 2021-01-01
        assert_eq!(claims.client_id, "client123");
        assert_eq!(claims.token_use, "id");
    }

    #[test]
    fn test_parse_token_claims_empty_token() {
        let key = DecodingKey::from_secret(b"test-secret");
        let validation = Validation::new(Algorithm::RS256);

        #[derive(serde::Deserialize, Debug)]
        struct TestClaims {}

        let result = TokenParser::parse_token_claims::<TestClaims>("", &key, &validation);
        assert!(result.is_err());
        assert!(matches!(result.unwrap_err(), JwtError::MissingToken));
    }

    #[test]
    fn test_parse_token_claims_invalid_format() {
        let key = DecodingKey::from_secret(b"test-secret");
        let validation = Validation::new(Algorithm::RS256);

        #[derive(serde::Deserialize, Debug)]
        struct TestClaims {}

        // Token with only one part
        let result = TokenParser::parse_token_claims::<TestClaims>("header", &key, &validation);
        assert!(result.is_err());
        assert!(matches!(result.unwrap_err(), JwtError::ParseError { .. }));

        // Token with two parts
        let result =
            TokenParser::parse_token_claims::<TestClaims>("header.payload", &key, &validation);
        assert!(result.is_err());
        assert!(matches!(result.unwrap_err(), JwtError::ParseError { .. }));
    }
    

    #[test]
    fn test_extract_claim_from_token() {
        // Create a test token with valid payload
        let header = base64_encode(r#"{"alg":"RS256","kid":"test-key-id"}"#);
        let payload = base64_encode(
            r#"{"sub":"user123","iss":"https://example.com","exp":4070908800,"iat":1609459200,"client_id":"client123","token_use":"id"}"#,
        );
        let token = create_test_token(&header, &payload, "signature");

        // Extract string claim
        let sub: String = TokenParser::extract_claim_from_token(&token, "sub").unwrap();
        assert_eq!(sub, "user123");

        // Extract numeric claim
        let exp: u64 = TokenParser::extract_claim_from_token(&token, "exp").unwrap();
        assert_eq!(exp, 4070908800);

        // Extract non-existent claim
        let result = TokenParser::extract_claim_from_token::<String>(&token, "non_existent");
        assert!(result.is_err());
        assert!(matches!(result.unwrap_err(), JwtError::ParseError { .. }));
    }
}