Restructure: move src to core/ crate, move cmd/supervisor.rs to core/src/bin/supervisor.rs, create workspace

This commit is contained in:
Timur Gordon
2025-11-06 23:30:18 +01:00
parent 0a617ad359
commit 6d518599b8
39 changed files with 824 additions and 140 deletions

134
core/src/auth.rs Normal file
View File

@@ -0,0 +1,134 @@
//! Authentication and API key management
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use uuid::Uuid;
/// API key scope/permission level
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ApiKeyScope {
/// Full access - can manage keys, runners, jobs
Admin,
/// Can register new runners
Registrar,
/// Can create and manage jobs
User,
}
impl ApiKeyScope {
pub fn as_str(&self) -> &'static str {
match self {
ApiKeyScope::Admin => "admin",
ApiKeyScope::Registrar => "registrar",
ApiKeyScope::User => "user",
}
}
}
/// An API key with metadata
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApiKey {
/// The actual key value (UUID or custom string)
pub key: String,
/// Human-readable name for the key
pub name: String,
/// Permission scope
pub scope: ApiKeyScope,
/// When the key was created
pub created_at: String,
/// Optional expiration timestamp
pub expires_at: Option<String>,
}
impl ApiKey {
/// Create a new API key with a generated UUID
pub fn new(name: String, scope: ApiKeyScope) -> Self {
Self {
key: Uuid::new_v4().to_string(),
name,
scope,
created_at: chrono::Utc::now().to_rfc3339(),
expires_at: None,
}
}
/// Create a new API key with a specific key value
pub fn with_key(key: String, name: String, scope: ApiKeyScope) -> Self {
Self {
key,
name,
scope,
created_at: chrono::Utc::now().to_rfc3339(),
expires_at: None,
}
}
}
/// API key store
#[derive(Debug, Clone, Default)]
pub struct ApiKeyStore {
/// Map of key -> ApiKey
keys: HashMap<String, ApiKey>,
}
impl ApiKeyStore {
pub fn new() -> Self {
Self {
keys: HashMap::new(),
}
}
/// Add a new API key
pub fn add_key(&mut self, key: ApiKey) {
self.keys.insert(key.key.clone(), key);
}
/// Remove an API key by its key value
pub fn remove_key(&mut self, key: &str) -> Option<ApiKey> {
self.keys.remove(key)
}
/// Get an API key by its key value
pub fn get_key(&self, key: &str) -> Option<&ApiKey> {
self.keys.get(key)
}
/// Verify a key and return its metadata if valid
pub fn verify_key(&self, key: &str) -> Option<&ApiKey> {
self.get_key(key)
}
/// List all keys with a specific scope
pub fn list_keys_by_scope(&self, scope: ApiKeyScope) -> Vec<&ApiKey> {
self.keys
.values()
.filter(|k| k.scope == scope)
.collect()
}
/// List all keys
pub fn list_all_keys(&self) -> Vec<&ApiKey> {
self.keys.values().collect()
}
/// Count keys by scope
pub fn count_by_scope(&self, scope: ApiKeyScope) -> usize {
self.keys.values().filter(|k| k.scope == scope).count()
}
/// Bootstrap with an initial admin key
pub fn bootstrap_admin_key(&mut self, name: String) -> ApiKey {
let key = ApiKey::new(name, ApiKeyScope::Admin);
self.add_key(key.clone());
key
}
}
/// Response for auth verification
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuthVerifyResponse {
pub valid: bool,
pub name: String,
pub scope: String,
}