feat: initial source commit

This commit is contained in:
2026-03-08 23:33:41 -07:00
commit ffddbc2e5e
10 changed files with 3610 additions and 0 deletions
+107
View File
@@ -0,0 +1,107 @@
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); }
}
}
}
}