end to end job management support
This commit is contained in:
@@ -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"
|
||||
|
234
interfaces/websocket/examples/src/circle_auth.rs
Normal file
234
interfaces/websocket/examples/src/circle_auth.rs
Normal 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");
|
||||
}
|
Reference in New Issue
Block a user