#![allow(unused_variables)]
use axum::{
extract::{Json, Path},
http::StatusCode,
response::IntoResponse,
};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "snake_case")]
pub enum ApiKeyScope {
ReasoningRead,
ReasoningWrite,
ProfileRead,
ProfileWrite,
SettingsRead,
SettingsWrite,
ExportCreate,
Admin,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApiKey {
pub id: Uuid,
pub name: String,
pub prefix: String,
#[serde(skip_serializing)]
pub key_hash: String,
pub user_id: Uuid,
pub scopes: Vec<ApiKeyScope>,
pub created_at: DateTime<Utc>,
pub last_used: Option<DateTime<Utc>>,
pub expires_at: Option<DateTime<Utc>>,
pub active: bool,
pub rate_limit: Option<u32>,
pub usage_count: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApiKeyCreated {
pub id: Uuid,
pub name: String,
pub key: String,
pub prefix: String,
pub scopes: Vec<ApiKeyScope>,
pub created_at: DateTime<Utc>,
pub expires_at: Option<DateTime<Utc>>,
}
#[allow(dead_code)] pub struct ApiKeyService {
max_keys_per_user: usize,
}
impl ApiKeyService {
pub fn new(max_keys_per_user: usize) -> Self {
Self { max_keys_per_user }
}
pub fn generate_key() -> String {
use rand::Rng;
let mut rng = rand::rng();
let key: String = (0..32)
.map(|_| {
let idx = rng.random_range(0..62);
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
.chars()
.nth(idx)
.unwrap()
})
.collect();
format!("rk_{}", key)
}
pub fn hash_key(key: &str) -> String {
use sha2::{Digest, Sha256};
let mut hasher = Sha256::new();
hasher.update(key.as_bytes());
format!("{:x}", hasher.finalize())
}
pub async fn create_key(
&self,
user_id: Uuid,
name: String,
scopes: Vec<ApiKeyScope>,
expires_at: Option<DateTime<Utc>>,
rate_limit: Option<u32>,
) -> Result<ApiKeyCreated, ApiKeyError> {
let raw_key = Self::generate_key();
let key_hash = Self::hash_key(&raw_key);
let prefix = raw_key[..11].to_string();
let created = ApiKeyCreated {
id: Uuid::new_v4(),
name,
key: raw_key,
prefix,
scopes,
created_at: Utc::now(),
expires_at,
};
Ok(created)
}
pub async fn list_keys(&self, user_id: Uuid) -> Result<Vec<ApiKey>, ApiKeyError> {
Ok(vec![])
}
pub async fn revoke_key(&self, user_id: Uuid, key_id: Uuid) -> Result<(), ApiKeyError> {
Ok(())
}
pub async fn rotate_key(
&self,
user_id: Uuid,
key_id: Uuid,
) -> Result<ApiKeyCreated, ApiKeyError> {
Err(ApiKeyError::NotFound)
}
pub async fn validate_key(&self, raw_key: &str) -> Result<ApiKey, ApiKeyError> {
let key_hash = Self::hash_key(raw_key);
Err(ApiKeyError::InvalidKey)
}
pub async fn record_usage(&self, key_id: Uuid) -> Result<(), ApiKeyError> {
Ok(())
}
}
impl Default for ApiKeyService {
fn default() -> Self {
Self::new(10)
}
}
#[derive(Debug, thiserror::Error)]
pub enum ApiKeyError {
#[error("API key not found")]
NotFound,
#[error("Invalid API key")]
InvalidKey,
#[error("API key expired")]
Expired,
#[error("API key revoked")]
Revoked,
#[error("Rate limit exceeded")]
RateLimitExceeded,
#[error("Insufficient permissions")]
InsufficientScope,
#[error("Maximum keys exceeded")]
MaxKeysExceeded,
#[error("Database error: {0}")]
DatabaseError(String),
}
pub mod handlers {
use super::*;
#[derive(Debug, Deserialize)]
pub struct CreateKeyRequest {
pub name: String,
pub scopes: Vec<ApiKeyScope>,
#[serde(default)]
pub expires_in_days: Option<u32>,
pub rate_limit: Option<u32>,
}
pub async fn list_keys() -> impl IntoResponse {
(StatusCode::OK, Json(serde_json::json!({"keys": []})))
}
pub async fn create_key(Json(req): Json<CreateKeyRequest>) -> impl IntoResponse {
let raw_key = ApiKeyService::generate_key();
let prefix = raw_key[..11].to_string();
let response = ApiKeyCreated {
id: Uuid::new_v4(),
name: req.name,
key: raw_key,
prefix,
scopes: req.scopes,
created_at: Utc::now(),
expires_at: req
.expires_in_days
.map(|d| Utc::now() + chrono::Duration::days(d as i64)),
};
(StatusCode::CREATED, Json(response))
}
pub async fn revoke_key(Path(id): Path<Uuid>) -> impl IntoResponse {
(
StatusCode::OK,
Json(serde_json::json!({"success": true, "revoked_key_id": id})),
)
}
pub async fn rotate_key(Path(id): Path<Uuid>) -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
}