use anyhow::Result; use sqlx::PgPool; use uuid::Uuid; const KEY_PREFIX: &str = "sk_"; /// Generate a new API key: `sk_<64 hex chars>` = 67 characters total. pub fn generate_api_key() -> String { use rand::RngExt; let mut bytes = [0u8; 32]; rand::rng().fill(&mut bytes); let hex: String = bytes.iter().map(|b| format!("{:02x}", b)).collect(); format!("{}{}", KEY_PREFIX, hex) } /// Return the user's existing API key, or generate and store a new one if NULL. pub async fn ensure_api_key(pool: &PgPool, user_id: Uuid) -> Result { let existing: Option<(Option,)> = sqlx::query_as("SELECT api_key FROM users WHERE id = $1") .bind(user_id) .fetch_optional(pool) .await?; if let Some((Some(key),)) = existing { return Ok(key); } let new_key = generate_api_key(); sqlx::query("UPDATE users SET api_key = $1 WHERE id = $2") .bind(&new_key) .bind(user_id) .execute(pool) .await?; Ok(new_key) } /// Generate a fresh API key for the user, replacing the old one. pub async fn regenerate_api_key(pool: &PgPool, user_id: Uuid) -> Result { let new_key = generate_api_key(); sqlx::query("UPDATE users SET api_key = $1 WHERE id = $2") .bind(&new_key) .bind(user_id) .execute(pool) .await?; Ok(new_key) } /// Validate a Bearer token. Returns the `user_id` if the key matches. pub async fn validate_api_key(pool: &PgPool, raw_key: &str) -> Result> { let row: Option<(Uuid,)> = sqlx::query_as("SELECT id FROM users WHERE api_key = $1") .bind(raw_key) .fetch_optional(pool) .await?; Ok(row.map(|(id,)| id)) }