end to end job management support

This commit is contained in:
Timur Gordon
2025-07-30 08:36:55 +02:00
parent 7d7ff0f0ab
commit 32c2cbe0cc
20 changed files with 2686 additions and 442 deletions

View File

@@ -10,6 +10,7 @@ tokio = { version = "1.0", features = ["full"] }
k256 = { version = "0.13", features = ["ecdsa", "sha256"] }
rand = "0.8"
hex = "0.4"
env_logger = "0.10"
[[bin]]
name = "ping"
@@ -19,6 +20,10 @@ path = "src/ping.rs"
name = "auth"
path = "src/auth.rs"
[[bin]]
name = "circle_auth"
path = "src/circle_auth.rs"
[[bin]]
name = "play"
path = "src/play.rs"

View File

@@ -0,0 +1,234 @@
use hero_websocket_client::CircleWsClientBuilder;
use hero_websocket_server::ServerBuilder;
use tokio::signal;
use tokio::time::{sleep, Duration};
use k256::ecdsa::SigningKey;
use k256::elliptic_curve::sec1::ToEncodedPoint;
use rand::rngs::OsRng;
use std::collections::HashMap;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
env_logger::init();
println!("🔗 Circle-based Authentication Example");
println!("=====================================");
// Generate keypairs for different users
let (alice_pubkey, alice_privkey) = generate_keypair();
let (bob_pubkey, bob_privkey) = generate_keypair();
let (charlie_pubkey, charlie_privkey) = generate_keypair();
let (eve_pubkey, eve_privkey) = generate_keypair(); // Not in any circle
println!("👥 Generated test users:");
println!(" Alice: {}...{}", &alice_pubkey[..10], &alice_pubkey[alice_pubkey.len()-10..]);
println!(" Bob: {}...{}", &bob_pubkey[..10], &bob_pubkey[bob_pubkey.len()-10..]);
println!(" Charlie: {}...{}", &charlie_pubkey[..10], &charlie_pubkey[charlie_pubkey.len()-10..]);
println!(" Eve: {}...{} (unauthorized)", &eve_pubkey[..10], &eve_pubkey[eve_pubkey.len()-10..]);
// Define circles and their members
let mut circles = HashMap::new();
// Circle "alpha" - Alice and Bob are members
circles.insert("alpha".to_string(), vec![
alice_pubkey.clone(),
bob_pubkey.clone(),
]);
// Circle "beta" - Only Charlie is a member
circles.insert("beta".to_string(), vec![
charlie_pubkey.clone(),
]);
// Circle "gamma" - Alice and Charlie are members
circles.insert("gamma".to_string(), vec![
alice_pubkey.clone(),
charlie_pubkey.clone(),
]);
println!("\n🔐 Circle memberships:");
for (circle_id, members) in &circles {
println!(" Circle '{}': {} members", circle_id, members.len());
for member in members {
let name = if member == &alice_pubkey { "Alice" }
else if member == &bob_pubkey { "Bob" }
else if member == &charlie_pubkey { "Charlie" }
else { "Unknown" };
println!(" - {} ({}...{})", name, &member[..10], &member[member.len()-10..]);
}
}
// Build server with circle-based authentication
let server = ServerBuilder::new()
.host("127.0.0.1")
.port(8443)
.redis_url("redis://localhost:6379")
.worker_id("circle_test")
.with_auth()
.circles(circles)
.build()?;
// Start server
println!("\n🚀 Starting server with circle-based authentication...");
let (server_task, server_handle) = server.spawn_circle_server().map_err(|e| {
eprintln!("Failed to start server: {}", e);
e
})?;
// Setup signal handling for clean shutdown
let server_handle_clone = server_handle.clone();
tokio::spawn(async move {
signal::ctrl_c().await.expect("Failed to listen for Ctrl+C");
println!("\n🔌 Shutting down...");
server_handle_clone.stop(true).await;
std::process::exit(0);
});
// Brief pause for server startup
sleep(Duration::from_millis(500)).await;
println!("\n🧪 Testing authentication scenarios:");
println!("===================================");
// Test 1: Alice connecting to circle "alpha" (should succeed)
println!("\n📋 Test 1: Alice → Circle 'alpha' (authorized)");
test_authentication("Alice", "alpha", &alice_privkey).await?;
// Test 2: Bob connecting to circle "alpha" (should succeed)
println!("\n📋 Test 2: Bob → Circle 'alpha' (authorized)");
test_authentication("Bob", "alpha", &bob_privkey).await?;
// Test 3: Charlie connecting to circle "beta" (should succeed)
println!("\n📋 Test 3: Charlie → Circle 'beta' (authorized)");
test_authentication("Charlie", "beta", &charlie_privkey).await?;
// Test 4: Alice connecting to circle "beta" (should fail - not a member)
println!("\n📋 Test 4: Alice → Circle 'beta' (unauthorized - not a member)");
test_authentication_failure("Alice", "beta", &alice_privkey).await;
// Test 5: Eve connecting to circle "alpha" (should fail - not a member)
println!("\n📋 Test 5: Eve → Circle 'alpha' (unauthorized - not a member)");
test_authentication_failure("Eve", "alpha", &eve_privkey).await;
// Test 6: Charlie connecting to circle "gamma" (should succeed)
println!("\n📋 Test 6: Charlie → Circle 'gamma' (authorized)");
test_authentication("Charlie", "gamma", &charlie_privkey).await?;
// Test 7: Bob connecting to non-existent circle (should fail)
println!("\n📋 Test 7: Bob → Circle 'nonexistent' (unauthorized - circle doesn't exist)");
test_authentication_failure("Bob", "nonexistent", &bob_privkey).await;
println!("\n✅ All tests completed!");
println!("\n💡 Summary:");
println!(" - Users can only authenticate to circles they are members of");
println!(" - Circle membership is checked after signature verification");
println!(" - Unauthorized access attempts are properly rejected");
println!(" - Each circle operates independently with its own member list");
// Clean shutdown
server_handle.stop(true).await;
println!("\n🔌 Server stopped. Example complete!");
Ok(())
}
fn generate_keypair() -> (String, String) {
let signing_key = SigningKey::random(&mut OsRng);
let verifying_key = signing_key.verifying_key();
let public_key_bytes = verifying_key.to_encoded_point(false).as_bytes().to_vec();
let private_key_bytes = signing_key.to_bytes().to_vec();
(hex::encode(public_key_bytes), hex::encode(private_key_bytes))
}
async fn test_authentication(
user_name: &str,
circle_name: &str,
private_key: &str,
) -> Result<(), Box<dyn std::error::Error>> {
println!(" 📤 {} connecting to circle '{}'...", user_name, circle_name);
let mut client = CircleWsClientBuilder::new(format!("ws://localhost:8443/{}", circle_name))
.with_keypair(private_key.to_string())
.build();
// Connect
match client.connect().await {
Ok(_) => println!(" ✅ Connection established"),
Err(e) => {
println!(" ❌ Connection failed: {}", e);
return Err(e.into());
}
}
// Authenticate
print!(" 📤 Authenticating... ");
match client.authenticate().await {
Ok(response) => {
println!("✅ Success!");
println!(" Response: {}", response);
}
Err(e) => {
println!("❌ Failed: {}", e);
client.disconnect().await;
return Err(e.into());
}
}
// Test whoami to confirm authentication
print!(" 📤 Testing whoami... ");
match client.whoami().await {
Ok(response) => {
println!("✅ Success!");
println!(" Response: {}", response);
}
Err(e) => {
println!("❌ Failed: {}", e);
client.disconnect().await;
return Err(e.into());
}
}
// Disconnect
client.disconnect().await;
println!(" 🔌 Disconnected");
Ok(())
}
async fn test_authentication_failure(
user_name: &str,
circle_name: &str,
private_key: &str,
) {
println!(" 📤 {} connecting to circle '{}'...", user_name, circle_name);
let mut client = CircleWsClientBuilder::new(format!("ws://localhost:8443/{}", circle_name))
.with_keypair(private_key.to_string())
.build();
// Connect
match client.connect().await {
Ok(_) => println!(" ✅ Connection established"),
Err(e) => {
println!(" ❌ Connection failed: {}", e);
return;
}
}
// Authenticate (expecting failure)
print!(" 📤 Authenticating... ");
match client.authenticate().await {
Ok(response) => {
println!("❌ Unexpected success: {}", response);
println!(" This should have failed!");
}
Err(e) => {
println!("✅ Expected failure!");
println!(" Error: {}", e);
}
}
// Disconnect
client.disconnect().await;
println!(" 🔌 Disconnected");
}