use anyhow::{anyhow, Result};
use std::fs;
use std::path::PathBuf;
use crate::jwt::{self, VerifyKeyData, VerifyOptions};
use crate::utils;
pub fn execute(
token: &str,
secret: Option<&str>,
private_key_path: Option<&PathBuf>,
validate_exp: bool,
) {
match verify_token(token, secret, private_key_path, validate_exp) {
Ok(is_valid) => {
if is_valid {
utils::log_success("Token is valid.");
} else {
utils::log_error("Token is invalid.".to_string());
}
}
Err(e) => {
utils::log_error(format!("JWT Verification Error: {e}"));
if let Some(jwt_error) = e.downcast_ref::<jwt::JwtError>() {
match jwt_error {
jwt::JwtError::InvalidSignature => {
utils::log_error(
"This could be due to an incorrect secret or key.".to_string(),
);
}
jwt::JwtError::ExpiredSignature => {
utils::log_error(
"The token has expired. Check the 'exp' claim.".to_string(),
);
}
jwt::JwtError::ImmatureSignature => {
utils::log_error(
"The token is not yet valid. Check the 'nbf' claim.".to_string(),
);
}
jwt::JwtError::InvalidAlgorithm => {
utils::log_error("The token's algorithm does not match the expected algorithm or the key provided.".to_string());
}
_ => {
utils::log_error(
"An unknown error occurred during JWT verification.".to_string(),
);
}
}
} else {
let err_msg = e.to_string().to_lowercase();
if err_msg.contains("invalid signature") {
utils::log_error(
"This could be due to an incorrect secret or key.".to_string(),
);
} else if err_msg.contains("expired") {
utils::log_error("The token has expired. Check the 'exp' claim.".to_string());
} else if err_msg.contains("immature") || err_msg.contains("not yet valid") {
utils::log_error(
"The token is not yet valid. Check the 'nbf' claim.".to_string(),
);
} else if err_msg.contains("algorithm") {
utils::log_error("The token's algorithm does not match the expected algorithm or the key provided.".to_string());
} else {
utils::log_error(
"An unknown error occurred during JWT verification.".to_string(),
);
}
}
utils::log_error("e.g jwt-hack verify {JWT_CODE} --secret={YOUR_SECRET}".to_string());
utils::log_error(
"or with RSA/ECDSA: jwt-hack verify {JWT_CODE} --private-key=key.pem".to_string(),
);
}
}
}
fn verify_token(
token: &str,
secret: Option<&str>,
private_key_path: Option<&PathBuf>,
validate_exp: bool,
) -> Result<bool> {
let key_data: VerifyKeyData;
let validate_nbf = false;
let private_key_content: String;
if let Some(pk_path) = private_key_path {
private_key_content = fs::read_to_string(pk_path)
.map_err(|e| anyhow!("Failed to read private key from {:?}: {}", pk_path, e))?;
key_data = VerifyKeyData::PublicKeyPem(&private_key_content);
} else if let Some(s) = secret {
key_data = VerifyKeyData::Secret(s);
} else {
let decoded_unverified = jwt::decode(token)?;
if decoded_unverified
.header
.get("alg")
.and_then(|v| v.as_str())
== Some("none")
{
key_data = VerifyKeyData::Secret("");
} else {
return Err(anyhow!("No secret or private key provided for a token that is not using 'none' algorithm. Please provide --secret or --private-key."));
}
}
let options = VerifyOptions {
key_data,
validate_exp, validate_nbf, leeway: 0, };
jwt::verify_with_options(token, &options)
}
#[cfg(test)]
mod tests {
use super::*;
use chrono::{Duration, Utc};
use serde_json::json;
use std::fs::File;
use std::io::Write;
use tempfile::tempdir;
#[test]
fn test_execute() {
let claims = json!({
"sub": "test_user"
});
let token = jwt::encode(&claims, "", "HS256").expect("Failed to create test token");
let result = std::panic::catch_unwind(|| {
execute(&token, None, None, false);
});
assert!(result.is_ok(), "execute() panicked with valid token");
}
#[test]
fn test_execute_with_secret() {
let secret = "test_secret";
let claims = json!({"sub": "test_user"});
let options = jwt::EncodeOptions {
algorithm: "HS256",
key_data: jwt::KeyData::Secret(secret),
header_params: None,
compress_payload: false,
};
let token =
jwt::encode_with_options(&claims, &options).expect("Failed to create test token");
let result = std::panic::catch_unwind(|| {
execute(&token, Some(secret), None, false);
});
assert!(
result.is_ok(),
"execute() panicked with valid token and secret"
);
}
#[test]
fn test_verify_token_with_secret() {
let secret = "test_secret";
let claims = json!({"sub": "test_user"});
let options = jwt::EncodeOptions {
algorithm: "HS256",
key_data: jwt::KeyData::Secret(secret),
header_params: None,
compress_payload: false,
};
let token =
jwt::encode_with_options(&claims, &options).expect("Failed to create test token");
let result = verify_token(&token, Some(secret), None, false);
assert!(
result.is_ok(),
"verify_token failed with valid token and secret"
);
assert!(
result.unwrap(),
"Token verification should succeed with correct secret"
);
let result = verify_token(&token, Some("wrong_secret"), None, false);
assert!(
result.is_ok(),
"verify_token should not error with wrong secret"
);
assert!(
!result.unwrap(),
"Token verification should fail with incorrect secret"
);
}
#[test]
fn test_verify_token_none_algorithm() {
let claims = json!({"sub": "test_user"});
let options = jwt::EncodeOptions {
algorithm: "none",
key_data: jwt::KeyData::None,
header_params: None,
compress_payload: false,
};
let token =
jwt::encode_with_options(&claims, &options).expect("Failed to create test token");
let result = verify_token(&token, None, None, false);
assert!(
result.is_ok(),
"verify_token failed with 'none' algorithm token"
);
assert!(
result.unwrap(),
"Token with 'none' algorithm should verify without secret"
);
}
#[test]
fn test_verify_token_with_expiration() {
let now = Utc::now();
let claims = json!({
"sub": "test_user",
"exp": (now - Duration::hours(1)).timestamp()
});
let token = jwt::encode(&claims, "", "HS256").expect("Failed to create test token");
let result = verify_token(&token, None, None, true);
assert!(
result.is_err() || (result.is_ok() && !result.unwrap()),
"Expired token should fail validation when validate_exp is true"
);
}
#[test]
fn test_verify_token_with_private_key() {
let dir = tempdir().expect("Failed to create temp directory");
let private_key_path = dir.path().join("private_key.pem");
let sample_key = "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADALBgkqhkiG9w0BAQEEggSpMIIEpQIBAAKCAQEAn\n-----END PRIVATE KEY-----";
File::create(&private_key_path)
.expect("Failed to create temp file")
.write_all(sample_key.as_bytes())
.expect("Failed to write to temp file");
let token = "header.payload.signature";
let result = verify_token(token, None, Some(&private_key_path), false);
assert!(
result.is_err(),
"verify_token with invalid key should return an error"
);
dir.close().expect("Failed to clean up temp directory");
}
}