use blvm_node::rpc::auth::{RpcAuthManager, UserId};
use hyper::HeaderMap;
use std::net::SocketAddr;
#[tokio::test]
async fn test_auth_bypass_attempt_empty_token() {
let manager = RpcAuthManager::new(true);
let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap();
let headers = HeaderMap::new();
let result = manager.authenticate_request(&headers, addr).await;
assert!(result.user_id.is_none());
assert!(result.requires_auth);
assert!(result.error.is_some());
}
#[tokio::test]
async fn test_auth_bypass_attempt_malformed_token() {
let manager = RpcAuthManager::new(true);
let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap();
let mut headers = HeaderMap::new();
headers.insert("authorization", "InvalidFormat token".parse().unwrap());
let result = manager.authenticate_request(&headers, addr).await;
assert!(result.user_id.is_none());
assert!(result.requires_auth);
}
#[tokio::test]
async fn test_auth_bypass_attempt_invalid_token() {
let manager = RpcAuthManager::new(true);
let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap();
let mut headers = HeaderMap::new();
headers.insert("authorization", "Bearer invalid-token-123".parse().unwrap());
let result = manager.authenticate_request(&headers, addr).await;
assert!(result.user_id.is_none());
assert!(result.requires_auth);
assert!(result.error.is_some());
}
#[tokio::test]
async fn test_auth_lockout_after_repeated_failures() {
let manager = RpcAuthManager::new(true);
let addr: SocketAddr = "127.0.0.1:8332".parse().unwrap();
let mut headers = HeaderMap::new();
headers.insert("authorization", "Bearer wrong-token".parse().unwrap());
for _ in 0..5 {
let result = manager.authenticate_request(&headers, addr).await;
assert!(result.user_id.is_none());
}
let blocked = manager.authenticate_request(&headers, addr).await;
assert!(blocked.user_id.is_none());
assert!(
blocked
.error
.as_ref()
.is_some_and(|e| e.contains("Too many authentication failures")),
"expected lockout message, got {:?}",
blocked.error
);
}
#[tokio::test]
async fn test_ckpool_basic_auth_not_admin_without_admin_tokens() {
let manager = RpcAuthManager::new(true);
manager
.set_basic_auth(Some("ckpool".to_string()), "pool-secret".to_string())
.await;
let mut headers = HeaderMap::new();
headers.insert(
"authorization",
"Basic Y2twb29sOnBvb2wtc2VjcmV0".parse().unwrap(), );
let addr: SocketAddr = "127.0.0.1:8332".parse().unwrap();
let auth = manager.authenticate_request(&headers, addr).await;
assert!(auth.user_id.is_some(), "Basic auth should succeed");
assert!(
!manager.is_user_admin(auth.user_id.as_ref().unwrap()).await,
"password-only Basic auth must not be admin when admin_tokens is empty"
);
}
#[tokio::test]
async fn test_ckpool_basic_auth_admin_when_password_in_admin_tokens() {
let manager = RpcAuthManager::new(true);
manager
.set_basic_auth(Some("ckpool".to_string()), "pool-secret".to_string())
.await;
manager
.add_admin_token("pool-secret".to_string())
.await
.unwrap();
let mut headers = HeaderMap::new();
headers.insert(
"authorization",
"Basic Y2twb29sOnBvb2wtc2VjcmV0".parse().unwrap(),
);
let addr: SocketAddr = "127.0.0.1:8332".parse().unwrap();
let auth = manager.authenticate_request(&headers, addr).await;
assert!(auth.user_id.is_some());
assert!(
manager.is_user_admin(auth.user_id.as_ref().unwrap()).await,
"password in admin_tokens must grant admin for Basic auth"
);
}
#[tokio::test]
async fn test_auth_valid_token_accepted() {
let manager = RpcAuthManager::new(true);
let token_str = "valid-token".to_string();
manager.add_token(token_str.clone()).await.unwrap();
let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap();
let mut headers = HeaderMap::new();
headers.insert(
"authorization",
format!("Bearer {token_str}").parse().unwrap(),
);
let result = manager.authenticate_request(&headers, addr).await;
assert!(result.user_id.is_some());
match result.user_id {
Some(UserId::Token(_)) => {} _ => panic!("Expected Token variant"),
}
assert!(result.error.is_none());
}
#[tokio::test]
async fn test_auth_no_auth_required_allows_access() {
let manager = RpcAuthManager::new(false);
let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap();
let headers = HeaderMap::new();
let result = manager.authenticate_request(&headers, addr).await;
assert!(!result.requires_auth);
match result.user_id {
Some(UserId::Ip(ip)) => assert_eq!(ip, addr),
_ => panic!("Expected IP-based user_id when auth not required"),
}
}
#[tokio::test]
async fn test_auth_token_revocation() {
let manager = RpcAuthManager::new(true);
let token_str = "revocable-token".to_string();
manager.add_token(token_str.clone()).await.unwrap();
let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap();
let mut headers = HeaderMap::new();
headers.insert(
"authorization",
format!("Bearer {token_str}").parse().unwrap(),
);
let result = manager.authenticate_request(&headers, addr).await;
assert!(result.user_id.is_some());
manager.remove_token(&token_str).await.unwrap();
let result = manager.authenticate_request(&headers, addr).await;
assert!(result.user_id.is_none());
assert!(result.error.is_some());
}
#[tokio::test]
async fn test_auth_concurrent_requests() {
use std::sync::Arc;
let manager = Arc::new(RpcAuthManager::new(true));
let token_str = "concurrent-token".to_string();
manager.add_token(token_str.clone()).await.unwrap();
let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap();
let mut headers = HeaderMap::new();
headers.insert(
"authorization",
format!("Bearer {token_str}").parse().unwrap(),
);
let mut handles = vec![];
for _ in 0..10 {
let manager_clone = Arc::clone(&manager);
let headers_clone = headers.clone();
let addr_clone = addr;
handles.push(tokio::spawn(async move {
manager_clone
.authenticate_request(&headers_clone, addr_clone)
.await
}));
}
for handle in handles {
let result = handle.await.unwrap();
assert!(result.user_id.is_some());
}
}
#[tokio::test]
async fn test_auth_brute_force_protection() {
let manager = RpcAuthManager::new(true);
let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap();
for i in 0..100 {
let mut headers = HeaderMap::new();
headers.insert(
"authorization",
format!("Bearer invalid-token-{i}").parse().unwrap(),
);
let result = manager.authenticate_request(&headers, addr).await;
assert!(result.user_id.is_none());
assert!(result.error.is_some());
}
}
#[tokio::test]
async fn test_rpc_rate_limiting_without_auth() {
let manager = RpcAuthManager::with_rate_limits(false, 5, 1);
let addr: SocketAddr = "127.0.0.1:9999".parse().unwrap();
for _ in 0..5 {
assert!(
manager
.check_ip_rate_limit_with_endpoint(addr, Some("rpc:getblockchaininfo"))
.await,
"Burst requests should be allowed"
);
}
assert!(
!manager
.check_ip_rate_limit_with_endpoint(addr, Some("rpc:getblockchaininfo"))
.await,
"Request beyond burst should be rate limited"
);
}
#[tokio::test]
async fn test_batch_rate_limiting() {
let manager = RpcAuthManager::with_rate_limits(false, 5, 1);
let addr: SocketAddr = "127.0.0.1:8888".parse().unwrap();
assert!(
!manager
.check_ip_rate_limit_with_endpoint_n(addr, Some("rpc:batch"), 10)
.await,
"Batch exceeding burst should be rate limited"
);
assert!(
manager
.check_ip_rate_limit_with_endpoint(addr, Some("rpc:getblockchaininfo"))
.await,
"Single request after failed batch should still work"
);
}
#[tokio::test]
async fn test_auth_case_sensitive_token() {
let manager = RpcAuthManager::new(true);
let token_str = "CaseSensitiveToken".to_string();
manager.add_token(token_str.clone()).await.unwrap();
let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap();
let mut headers = HeaderMap::new();
headers.insert(
"authorization",
format!("Bearer {token_str}").parse().unwrap(),
);
let result = manager.authenticate_request(&headers, addr).await;
assert!(result.user_id.is_some());
let mut headers2 = HeaderMap::new();
headers2.insert(
"authorization",
"Bearer casesensitivetoken".parse().unwrap(),
);
let result = manager.authenticate_request(&headers2, addr).await;
assert!(result.user_id.is_none());
}