Public Access
108 lines
3.8 KiB
Rust
108 lines
3.8 KiB
Rust
mod config;
|
|
mod db;
|
|
mod models;
|
|
|
|
use clap::{Parser, Subcommand};
|
|
use argon2::{Argon2, PasswordHasher, password_hash::{SaltString, rand_core::OsRng}};
|
|
use uuid::Uuid;
|
|
use rand::Rng;
|
|
|
|
#[derive(Parser)]
|
|
#[command(name = "gateway-cli", about = "Action Gateway admin CLI")]
|
|
struct Cli {
|
|
#[command(subcommand)]
|
|
command: Commands,
|
|
}
|
|
|
|
#[derive(Subcommand)]
|
|
enum Commands {
|
|
/// Add a user
|
|
AddUser {
|
|
username: String,
|
|
password: String,
|
|
},
|
|
/// List all users
|
|
ListUsers,
|
|
/// Remove a user
|
|
RemoveUser {
|
|
username: String,
|
|
},
|
|
/// Generate a new API key for the agent
|
|
GenApiKey {
|
|
#[arg(default_value = "agent")]
|
|
label: String,
|
|
},
|
|
/// List API keys
|
|
ListKeys,
|
|
}
|
|
|
|
fn main() {
|
|
let cli = Cli::parse();
|
|
let config = config::Config::load();
|
|
let conn = db::open(&config.database_path).expect("Failed to open database");
|
|
|
|
match cli.command {
|
|
Commands::AddUser { username, password } => {
|
|
let salt = SaltString::generate(&mut OsRng);
|
|
let hash = Argon2::default()
|
|
.hash_password(password.as_bytes(), &salt)
|
|
.unwrap()
|
|
.to_string();
|
|
conn.execute(
|
|
"INSERT INTO users (id, username, password_hash) VALUES (?1, ?2, ?3)",
|
|
[&Uuid::new_v4().to_string(), &username, &hash],
|
|
).unwrap();
|
|
println!("✅ User '{}' created.", username);
|
|
}
|
|
Commands::ListUsers => {
|
|
let mut stmt = conn.prepare("SELECT username, created_at FROM users ORDER BY created_at").unwrap();
|
|
let users: Vec<(String, String)> = stmt.query_map([], |row| Ok((row.get(0)?, row.get(1)?))).unwrap()
|
|
.filter_map(|r| r.ok()).collect();
|
|
if users.is_empty() {
|
|
println!("No users.");
|
|
} else {
|
|
println!("{:<20} {}", "Username", "Created");
|
|
println!("{}", "-".repeat(40));
|
|
for (u, c) in users { println!("{:<20} {}", u, c); }
|
|
}
|
|
}
|
|
Commands::RemoveUser { username } => {
|
|
let n = conn.execute("DELETE FROM users WHERE username = ?1", [&username]).unwrap();
|
|
if n > 0 { println!("✅ User '{}' removed.", username); }
|
|
else { println!("❌ User '{}' not found.", username); }
|
|
}
|
|
Commands::GenApiKey { label } => {
|
|
// Generate random key
|
|
let key: String = rand::thread_rng()
|
|
.sample_iter(&rand::distributions::Alphanumeric)
|
|
.take(48)
|
|
.map(char::from)
|
|
.collect();
|
|
let salt = SaltString::generate(&mut OsRng);
|
|
let hash = Argon2::default()
|
|
.hash_password(key.as_bytes(), &salt)
|
|
.unwrap()
|
|
.to_string();
|
|
conn.execute(
|
|
"INSERT INTO api_keys (id, key_hash, label) VALUES (?1, ?2, ?3)",
|
|
[&Uuid::new_v4().to_string(), &hash, &label],
|
|
).unwrap();
|
|
println!("✅ API key generated for '{}':", label);
|
|
println!("\n {}\n", key);
|
|
println!("Store this key securely — it won't be shown again.");
|
|
}
|
|
Commands::ListKeys => {
|
|
let mut stmt = conn.prepare("SELECT label, created_at FROM api_keys ORDER BY created_at").unwrap();
|
|
let keys: Vec<(String, String)> = stmt.query_map([], |row| Ok((row.get(0)?, row.get(1)?))).unwrap()
|
|
.filter_map(|r| r.ok()).collect();
|
|
if keys.is_empty() {
|
|
println!("No API keys.");
|
|
} else {
|
|
println!("{:<20} {}", "Label", "Created");
|
|
println!("{}", "-".repeat(40));
|
|
for (l, c) in keys { println!("{:<20} {}", l, c); }
|
|
}
|
|
}
|
|
}
|
|
}
|