use axum::{Json, Router, extract::State, routing::get}; use fc_common::repo; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; use uuid::Uuid; use crate::{auth_middleware::RequireAdmin, error::ApiError, state::AppState}; #[derive(Debug, Deserialize)] pub struct CreateApiKeyRequest { pub name: String, pub role: Option, } #[derive(Debug, Serialize)] pub struct CreateApiKeyResponse { pub id: Uuid, pub name: String, pub key: String, pub role: String, } #[derive(Debug, Serialize)] pub struct ApiKeyInfo { pub id: Uuid, pub name: String, pub role: String, pub created_at: chrono::DateTime, pub last_used_at: Option>, } #[must_use] pub fn hash_api_key(key: &str) -> String { let mut hasher = Sha256::new(); hasher.update(key.as_bytes()); hex::encode(hasher.finalize()) } async fn create_api_key( _auth: RequireAdmin, State(state): State, Json(input): Json, ) -> Result, ApiError> { let role = input.role.unwrap_or_else(|| "read-only".to_string()); // Generate a random API key let key = format!("fc_{}", Uuid::new_v4().to_string().replace('-', "")); let key_hash = hash_api_key(&key); let api_key = repo::api_keys::create(&state.pool, &input.name, &key_hash, &role) .await .map_err(ApiError)?; Ok(Json(CreateApiKeyResponse { id: api_key.id, name: api_key.name, key, // Only returned once at creation time role: api_key.role, })) } async fn list_api_keys( _auth: RequireAdmin, State(state): State, ) -> Result>, ApiError> { let keys = repo::api_keys::list(&state.pool).await.map_err(ApiError)?; let infos: Vec = keys .into_iter() .map(|k| { ApiKeyInfo { id: k.id, name: k.name, role: k.role, created_at: k.created_at, last_used_at: k.last_used_at, } }) .collect(); Ok(Json(infos)) } async fn delete_api_key( _auth: RequireAdmin, State(state): State, axum::extract::Path(id): axum::extract::Path, ) -> Result, ApiError> { repo::api_keys::delete(&state.pool, id) .await .map_err(ApiError)?; Ok(Json(serde_json::json!({ "deleted": true }))) } pub fn router() -> Router { Router::new() .route("/api-keys", get(list_api_keys).post(create_api_key)) .route("/api-keys/{id}", axum::routing::delete(delete_api_key)) }