use super::test_helpers::*;
use crate::parser;
use crate::AuthError;
use base64::Engine as _;
use jsonwebtoken::Algorithm;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_decode_header_valid() {
let token = create_mock_jwt_token();
let result = parser::JwtParser::decode_header(&token);
assert!(
result.is_ok(),
"Should successfully decode valid JWT header"
);
let header = result.expect("Should decode valid JWT header");
assert_eq!(header.alg, "ES256", "Algorithm should be ES256");
assert_eq!(
header.kid,
Some(TEST_KID.to_string()),
"Key ID should match test key ID"
);
assert_eq!(
header.typ,
Some("JWT".to_string()),
"Token type should be JWT"
);
}
#[test]
fn test_decode_header_invalid_inputs() {
assert!(
parser::JwtParser::decode_header("").is_err(),
"Empty token should be rejected"
);
assert!(
parser::JwtParser::decode_header(" ").is_err(),
"Whitespace-only token should be rejected"
);
assert!(
parser::JwtParser::decode_header("\t\n\r").is_err(),
"Token with only whitespace characters should be rejected"
);
assert!(
parser::JwtParser::decode_header("invalid.token").is_err(),
"Two-part token should be rejected"
);
assert!(
parser::JwtParser::decode_header("too.many.parts.here").is_err(),
"Four-part token should be rejected"
);
assert!(
parser::JwtParser::decode_header("single_part").is_err(),
"Single-part token should be rejected"
);
assert!(
parser::JwtParser::decode_header("no_dots_at_all").is_err(),
"Token without dots should be rejected"
);
assert!(
parser::JwtParser::decode_header(".payload.signature").is_err(),
"Empty header part should be rejected"
);
assert!(
parser::JwtParser::decode_header("header..signature").is_err(),
"Empty payload part should be rejected"
);
assert!(
parser::JwtParser::decode_header("header.payload.").is_err(),
"Empty signature part should be rejected"
);
assert!(
parser::JwtParser::decode_header("...").is_err(),
"All empty parts should be rejected"
);
let long_token = "a".repeat(10000);
assert!(
parser::JwtParser::decode_header(&long_token).is_err(),
"Excessively long token should be rejected"
);
let long_header = "a".repeat(2700); let long_payload = "b".repeat(2700); let long_signature = "c".repeat(2700); let boundary_token = format!("{long_header}.{long_payload}.{long_signature}"); assert!(
parser::JwtParser::decode_header(&boundary_token).is_err(),
"Token exceeding length limit should be rejected"
);
let valid_header = base64::engine::general_purpose::URL_SAFE_NO_PAD
.encode(r#"{"alg":"ES256","kid":"test"}"#);
let valid_payload = "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9";
let valid_signature = "signature";
let valid_token = format!("{valid_header}.{valid_payload}.{valid_signature}");
assert!(
parser::JwtParser::decode_header(&valid_token).is_ok(),
"Valid format token should parse header successfully"
);
}
#[test]
fn test_create_decoding_key_for_supabase_auth() {
let jwk = create_test_jwk();
let result = parser::JwtParser::create_decoding_key(&jwk);
assert!(
result.is_ok(),
"Should create decoding key from valid Supabase Auth JWK (ECC P-256)"
);
let mut invalid_jwk = create_test_jwk();
invalid_jwk.x = None; let result = parser::JwtParser::create_decoding_key(&invalid_jwk);
assert!(
matches!(result, Err(AuthError::InvalidKeyComponent(_))),
"Should reject JWK missing x coordinate for ECC P-256"
);
}
#[test]
fn test_parse_algorithm() {
let result = parser::JwtParser::parse_algorithm("ES256");
assert!(result.is_ok(), "ES256 algorithm should be supported");
assert_eq!(
result.expect("Should parse ES256 algorithm"),
Algorithm::ES256
);
assert!(
parser::JwtParser::parse_algorithm("RS256").is_err(),
"RS256 not supported by Supabase Auth"
);
assert!(
parser::JwtParser::parse_algorithm("HS256").is_err(),
"HS256 not supported by Supabase Auth"
);
assert!(
parser::JwtParser::parse_algorithm("PS256").is_err(),
"PS256 not supported by Supabase Auth"
);
assert!(
parser::JwtParser::parse_algorithm("ES384").is_err(),
"ES384 not supported by Supabase Auth"
);
assert!(
parser::JwtParser::parse_algorithm("ES512").is_err(),
"ES512 not supported by Supabase Auth"
);
assert!(
parser::JwtParser::parse_algorithm("none").is_err(),
"'none' algorithm forbidden for security"
);
assert!(
parser::JwtParser::parse_algorithm("INVALID").is_err(),
"Invalid algorithm should not be supported"
);
assert!(
parser::JwtParser::parse_algorithm("").is_err(),
"Empty algorithm should not be supported"
);
assert!(
parser::JwtParser::parse_algorithm("es256").is_err(),
"Should reject lowercase es256"
);
assert!(
parser::JwtParser::parse_algorithm("Es256").is_err(),
"Should reject mixed case Es256"
);
assert!(
parser::JwtParser::parse_algorithm("ES256").is_ok(),
"Should accept exact ES256"
);
}
#[test]
fn test_verify_and_decode_failure_invalid_signature() {
let jwk = create_test_jwk();
let decoding_key = parser::JwtParser::create_decoding_key(&jwk)
.expect("Should create decoding key from valid JWK");
let algorithm =
parser::JwtParser::parse_algorithm("ES256").expect("Should parse ES256 algorithm");
let invalid_token = "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InRlc3Qta2V5LWlkIn0.eyJzdWIiOiJ0ZXN0In0.invalidsignature";
let result = parser::JwtParser::verify_and_decode(invalid_token, &decoding_key, algorithm);
assert!(
result.is_err(),
"JWT validation should fail with an invalid signature"
);
let malformed_token =
"eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InRlc3Qta2V5LWlkIn0.eyJzdWIiOiJ0ZXN0In0.";
let result2 =
parser::JwtParser::verify_and_decode(malformed_token, &decoding_key, algorithm);
assert!(
result2.is_err(),
"JWT validation should fail with empty signature"
);
}
#[test]
fn test_algorithm_errors() {
let result = parser::JwtParser::parse_algorithm("INVALID");
assert!(matches!(result, Err(AuthError::InvalidAlgorithm)));
}
#[test]
fn test_header_parsing_errors() {
let result = parser::JwtParser::decode_header("");
assert!(result.is_err(), "Empty token should cause an error");
}
#[test]
fn test_jwk_validation_errors() {
let mut invalid_jwk = create_test_jwk();
invalid_jwk.x = None;
let result = parser::JwtParser::create_decoding_key(&invalid_jwk);
assert!(matches!(result, Err(AuthError::InvalidKeyComponent(_))));
}
#[test]
fn test_algorithm_support() {
let result = parser::JwtParser::parse_algorithm("ES256");
assert!(result.is_ok());
let unsupported = vec!["HS256", "RS256", "PS256"];
for alg in unsupported {
let result = parser::JwtParser::parse_algorithm(alg);
assert!(
result.is_err(),
"Should reject unsupported algorithm: {alg}"
);
}
}
#[test]
fn test_supabase_jwk_coordinate_validation() {
let mut jwk = create_test_jwk();
jwk.x = None;
let result = parser::JwtParser::create_decoding_key(&jwk);
assert!(
matches!(result, Err(AuthError::InvalidKeyComponent(_))),
"Should reject JWK missing x coordinate for Supabase Auth ECC P-256"
);
jwk.x = Some("ykCi3ZomyYBFS21ZKk6ajc56O1SUFzhCNp0ziDYd6mw".to_string());
jwk.y = None;
let result = parser::JwtParser::create_decoding_key(&jwk);
assert!(
matches!(result, Err(AuthError::InvalidKeyComponent(_))),
"Should reject JWK missing y coordinate for Supabase Auth ECC P-256"
);
}
#[test]
fn test_decode_header_malformed_base64() {
let invalid_token = "invalid_base64!@#.payload.signature";
let result = parser::JwtParser::decode_header(invalid_token);
assert!(result.is_err());
}
#[test]
fn test_decode_header_invalid_json() {
let invalid_json_b64 =
base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(b"{invalid json}");
let invalid_token = format!("{invalid_json_b64}.payload.signature");
let result = parser::JwtParser::decode_header(&invalid_token);
assert!(result.is_err());
}
#[test]
fn test_decode_header_unsupported_algorithm() {
let header = serde_json::json!({
"alg": "RS256",
"typ": "JWT",
"kid": "test-key-id"
});
let header_b64 =
base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(header.to_string().as_bytes());
let token = format!("{header_b64}.payload.signature");
let result = parser::JwtParser::decode_header(&token);
assert!(result.is_err());
}
#[test]
fn test_create_decoding_key_unsupported_curve() {
let mut jwk = create_test_jwk();
jwk.crv = Some("P-384".to_string());
let result = parser::JwtParser::create_decoding_key(&jwk);
assert!(
matches!(result, Err(AuthError::UnsupportedCurve(_))),
"Should reject P-384 curve (Supabase Auth only supports P-256)"
);
jwk.crv = Some("P-521".to_string());
let result2 = parser::JwtParser::create_decoding_key(&jwk);
assert!(
matches!(result2, Err(AuthError::UnsupportedCurve(_))),
"Should reject P-521 curve (Supabase Auth only supports P-256)"
);
jwk.crv = Some("secp256k1".to_string()); let result3 = parser::JwtParser::create_decoding_key(&jwk);
assert!(
matches!(result3, Err(AuthError::UnsupportedCurve(_))),
"Should reject secp256k1 curve (Supabase Auth only supports P-256)"
);
}
#[test]
fn test_create_decoding_key_invalid_coordinate_length() {
let mut jwk = create_test_jwk();
jwk.x = Some(base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(b"short"));
let result = parser::JwtParser::create_decoding_key(&jwk);
assert!(
matches!(result, Err(AuthError::InvalidKeyComponent(_))),
"Should reject invalid coordinate length for Supabase Auth ECC P-256"
);
jwk.x = Some(base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(vec![0u8; 64])); let result2 = parser::JwtParser::create_decoding_key(&jwk);
assert!(
matches!(result2, Err(AuthError::InvalidKeyComponent(_))),
"Should reject oversized coordinate for Supabase Auth ECC P-256"
);
}
#[test]
fn test_create_decoding_key_unsupported_key_type() {
let mut jwk = create_test_jwk();
jwk.kty = "RSA".to_string();
let result = parser::JwtParser::create_decoding_key(&jwk);
assert!(
matches!(result, Err(AuthError::UnsupportedKeyType(_))),
"Should reject RSA key type (Supabase Auth only supports EC)"
);
jwk.kty = "oct".to_string(); let result2 = parser::JwtParser::create_decoding_key(&jwk);
assert!(
matches!(result2, Err(AuthError::UnsupportedKeyType(_))),
"Should reject symmetric key type (Supabase Auth only supports EC)"
);
jwk.kty = "OKP".to_string(); let result3 = parser::JwtParser::create_decoding_key(&jwk);
assert!(
matches!(result3, Err(AuthError::UnsupportedKeyType(_))),
"Should reject OKP key type (Supabase Auth only supports EC)"
);
}
#[test]
fn test_decode_header_with_special_characters() {
let token_with_special_chars = "header.payload.signature@#$%";
let result = parser::JwtParser::decode_header(token_with_special_chars);
assert!(result.is_err());
}
#[test]
fn test_decode_header_maximum_length() {
let long_part = "a".repeat(2700); let long_token = format!("{long_part}.{long_part}.{long_part}");
let result = parser::JwtParser::decode_header(&long_token);
assert!(
result.is_err(),
"Token exceeding maximum length should be rejected"
);
let boundary_length = 8192;
let boundary_token = "a".repeat(boundary_length);
let result2 = parser::JwtParser::decode_header(&boundary_token);
assert!(
result2.is_err(),
"Token at exact boundary length should be rejected"
);
let over_boundary_token = "a".repeat(boundary_length + 1);
let result3 = parser::JwtParser::decode_header(&over_boundary_token);
assert!(
result3.is_err(),
"Token slightly over boundary should be rejected"
);
match result {
Err(AuthError::DecodeHeader) => {} _ => panic!("Expected DecodeHeader error for oversized token"),
}
}
#[test]
fn test_jwk_validation_comprehensive() {
let jwk = create_test_jwk();
assert_eq!(jwk.kty, "EC");
assert_eq!(jwk.alg, Some("ES256".to_string()));
assert_eq!(jwk.crv, Some("P-256".to_string()));
assert!(jwk.x.is_some());
assert!(jwk.y.is_some());
let result = parser::JwtParser::create_decoding_key(&jwk);
assert!(result.is_ok());
}
#[test]
fn test_supabase_algorithm_parsing_edge_cases() {
assert_eq!(
parser::JwtParser::parse_algorithm("ES256").expect("Should parse ES256"),
Algorithm::ES256
);
assert!(
parser::JwtParser::parse_algorithm("ES384").is_err(),
"ES384 not supported by Supabase Auth (uses P-384)"
);
assert!(
parser::JwtParser::parse_algorithm("ES512").is_err(),
"ES512 not supported by Supabase Auth (uses P-521)"
);
assert!(
parser::JwtParser::parse_algorithm("HS256").is_err(),
"HS256 not supported by Supabase Auth"
);
assert!(
parser::JwtParser::parse_algorithm("RS256").is_err(),
"RS256 not supported by Supabase Auth"
);
assert!(
parser::JwtParser::parse_algorithm("PS256").is_err(),
"PS256 not supported by Supabase Auth"
);
assert!(
parser::JwtParser::parse_algorithm("INVALID").is_err(),
"Invalid algorithm should be rejected"
);
assert!(
parser::JwtParser::parse_algorithm("").is_err(),
"Empty algorithm should be rejected"
);
assert!(
parser::JwtParser::parse_algorithm("none").is_err(),
"'none' algorithm forbidden for security"
);
assert!(
parser::JwtParser::parse_algorithm(" ES256 ").is_err(),
"Algorithm with whitespace should be rejected"
);
assert!(
parser::JwtParser::parse_algorithm("ES256\0").is_err(),
"Algorithm with null byte should be rejected"
);
}
#[test]
fn test_header_parsing_security() {
let malicious_headers = vec![
"eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9", "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9", ];
for header in malicious_headers {
let token = format!("{header}.payload.signature");
let result = parser::JwtParser::decode_header(&token);
if result.is_ok() {
let parsed_header = result.unwrap();
if parsed_header.alg != "ES256" {
assert!(parser::JwtParser::parse_algorithm(&parsed_header.alg).is_err());
}
}
}
}
#[test]
fn test_jwk_coordinate_edge_cases() {
let mut jwk = create_test_jwk();
jwk.x = None;
assert!(matches!(
parser::JwtParser::create_decoding_key(&jwk),
Err(AuthError::InvalidKeyComponent(_))
));
jwk = create_test_jwk();
jwk.y = None;
assert!(matches!(
parser::JwtParser::create_decoding_key(&jwk),
Err(AuthError::InvalidKeyComponent(_))
));
jwk = create_test_jwk();
jwk.x = Some("short".to_string());
assert!(matches!(
parser::JwtParser::create_decoding_key(&jwk),
Err(AuthError::Base64Decode(_))
));
jwk = create_test_jwk();
jwk.x = Some("invalid_base64!".to_string());
assert!(matches!(
parser::JwtParser::create_decoding_key(&jwk),
Err(AuthError::Base64Decode(_))
));
}
#[test]
fn test_token_format_variations() {
let invalid_tokens = vec![
("", "empty token"),
("single_part", "single part token"),
("two.parts", "two parts token"),
("too.many.parts.here.invalid", "five parts token"),
("...", "three empty parts"),
("valid..", "empty payload and signature"),
(".valid.", "empty header and signature"),
("..valid", "empty header and payload"),
("a.b.c.d.e.f", "six parts token"),
(".", "single dot"),
("..", "two dots only"),
];
for (token, description) in invalid_tokens {
let result = parser::JwtParser::decode_header(token);
assert!(
result.is_err(),
"Token '{token}' ({description}) should be invalid"
);
match result {
Err(_) => {} _ => panic!("Expected error for token: {token}"),
}
}
}
#[test]
fn test_supabase_parser_performance_and_security() {
let valid_token = create_mock_jwt_token();
let start = std::time::Instant::now();
for _ in 0..1000 {
let result = parser::JwtParser::decode_header(&valid_token);
assert!(
result.is_ok(),
"Valid Supabase Auth token should always be parsed successfully"
);
}
let duration = start.elapsed();
assert!(duration.as_millis() < 100, "1000 Supabase Auth JWT header parsing operations should complete within 100ms, took: {duration:?}");
let malicious_tokens = vec![
format!(
"{}.{}.{}",
"a".repeat(8000),
"b".repeat(8000),
"c".repeat(8000)
), format!("header.{}.signature", "x".repeat(7000)), format!("{}.payload.signature", "y".repeat(7000)), ];
for malicious_token in &malicious_tokens {
let start = std::time::Instant::now();
let result = parser::JwtParser::decode_header(malicious_token);
let duration = start.elapsed();
assert!(
result.is_err(),
"Malicious token should be rejected by Supabase Auth parser"
);
assert!(
duration.as_millis() < 10,
"Supabase Auth parser should quickly reject malicious tokens, took: {duration:?}"
);
}
}
#[test]
fn test_supabase_unicode_and_encoding_edge_cases() {
let unicode_tokens = vec![
"header.payload.签名", "header.payload.🔐", "header.payload.\u{0000}", "header.payload.\u{FEFF}", "header.payload.\u{200B}", ];
for token in unicode_tokens {
let result = parser::JwtParser::decode_header(token);
assert!(
result.is_err(),
"Supabase Auth parser should reject token with Unicode characters: {token}"
);
}
let control_char_tokens = vec![
"header.payload.\x00signature", "header.payload.\x01signature", "header.payload.\x1Fsignature", "header\x0A.payload.signature", "header.payload\x0D.signature", ];
for token in control_char_tokens {
let result = parser::JwtParser::decode_header(token);
assert!(
result.is_err(),
"Supabase Auth parser should reject token with control characters"
);
}
}
#[test]
fn test_supabase_base64_padding_variations() {
let padding_test_cases = vec![
("header=.payload.signature", "header with padding"),
("header.payload=.signature", "payload with padding"),
("header.payload.signature=", "signature with padding"),
("header==.payload.signature", "header with double padding"),
("header.payload==.signature", "payload with double padding"),
(
"header.payload.signature==",
"signature with double padding",
),
];
for (token, description) in padding_test_cases {
let result = parser::JwtParser::decode_header(token);
assert!(
result.is_err(),
"Supabase Auth JWT parser should reject token with padding characters: {description}"
);
if let Err(error) = result {
assert!(
matches!(error, AuthError::DecodeHeader | AuthError::InvalidToken),
"Expected DecodeHeader or InvalidToken error for {description}, got: {error:?}"
);
}
}
}
}