1#![allow(unused_variables)] use axum::{
15 extract::{Json, Path},
16 http::StatusCode,
17 response::IntoResponse,
18};
19use chrono::{DateTime, Utc};
20use serde::{Deserialize, Serialize};
21use uuid::Uuid;
22
23#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
25#[serde(rename_all = "snake_case")]
26pub enum ApiKeyScope {
27 ReasoningRead,
29 ReasoningWrite,
31 ProfileRead,
33 ProfileWrite,
35 SettingsRead,
37 SettingsWrite,
39 ExportCreate,
41 Admin,
43}
44
45#[derive(Debug, Clone, Serialize, Deserialize)]
47pub struct ApiKey {
48 pub id: Uuid,
50 pub name: String,
52 pub prefix: String,
54 #[serde(skip_serializing)]
56 pub key_hash: String,
57 pub user_id: Uuid,
59 pub scopes: Vec<ApiKeyScope>,
61 pub created_at: DateTime<Utc>,
63 pub last_used: Option<DateTime<Utc>>,
65 pub expires_at: Option<DateTime<Utc>>,
67 pub active: bool,
69 pub rate_limit: Option<u32>,
71 pub usage_count: u64,
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize)]
77pub struct ApiKeyCreated {
78 pub id: Uuid,
79 pub name: String,
80 pub key: String,
82 pub prefix: String,
83 pub scopes: Vec<ApiKeyScope>,
84 pub created_at: DateTime<Utc>,
85 pub expires_at: Option<DateTime<Utc>>,
86}
87
88#[allow(dead_code)] pub struct ApiKeyService {
91 max_keys_per_user: usize,
93}
94
95impl ApiKeyService {
96 pub fn new(max_keys_per_user: usize) -> Self {
97 Self { max_keys_per_user }
98 }
99
100 pub fn generate_key() -> String {
102 use rand::Rng;
103 let mut rng = rand::rng();
104 let key: String = (0..32)
105 .map(|_| {
106 let idx = rng.random_range(0..62);
107 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
108 .chars()
109 .nth(idx)
110 .unwrap()
111 })
112 .collect();
113 format!("rk_{}", key)
114 }
115
116 pub fn hash_key(key: &str) -> String {
118 use sha2::{Digest, Sha256};
119 let mut hasher = Sha256::new();
120 hasher.update(key.as_bytes());
121 format!("{:x}", hasher.finalize())
122 }
123
124 pub async fn create_key(
126 &self,
127 user_id: Uuid,
128 name: String,
129 scopes: Vec<ApiKeyScope>,
130 expires_at: Option<DateTime<Utc>>,
131 rate_limit: Option<u32>,
132 ) -> Result<ApiKeyCreated, ApiKeyError> {
133 let raw_key = Self::generate_key();
137 let key_hash = Self::hash_key(&raw_key);
138 let prefix = raw_key[..11].to_string(); let created = ApiKeyCreated {
141 id: Uuid::new_v4(),
142 name,
143 key: raw_key,
144 prefix,
145 scopes,
146 created_at: Utc::now(),
147 expires_at,
148 };
149
150 Ok(created)
151 }
152
153 pub async fn list_keys(&self, user_id: Uuid) -> Result<Vec<ApiKey>, ApiKeyError> {
155 Ok(vec![])
157 }
158
159 pub async fn revoke_key(&self, user_id: Uuid, key_id: Uuid) -> Result<(), ApiKeyError> {
161 Ok(())
163 }
164
165 pub async fn rotate_key(
167 &self,
168 user_id: Uuid,
169 key_id: Uuid,
170 ) -> Result<ApiKeyCreated, ApiKeyError> {
171 Err(ApiKeyError::NotFound)
173 }
174
175 pub async fn validate_key(&self, raw_key: &str) -> Result<ApiKey, ApiKeyError> {
177 let key_hash = Self::hash_key(raw_key);
178 Err(ApiKeyError::InvalidKey)
180 }
181
182 pub async fn record_usage(&self, key_id: Uuid) -> Result<(), ApiKeyError> {
184 Ok(())
186 }
187}
188
189impl Default for ApiKeyService {
190 fn default() -> Self {
191 Self::new(10)
192 }
193}
194
195#[derive(Debug, thiserror::Error)]
197pub enum ApiKeyError {
198 #[error("API key not found")]
199 NotFound,
200 #[error("Invalid API key")]
201 InvalidKey,
202 #[error("API key expired")]
203 Expired,
204 #[error("API key revoked")]
205 Revoked,
206 #[error("Rate limit exceeded")]
207 RateLimitExceeded,
208 #[error("Insufficient permissions")]
209 InsufficientScope,
210 #[error("Maximum keys exceeded")]
211 MaxKeysExceeded,
212 #[error("Database error: {0}")]
213 DatabaseError(String),
214}
215
216pub mod handlers {
218 use super::*;
219
220 #[derive(Debug, Deserialize)]
221 pub struct CreateKeyRequest {
222 pub name: String,
223 pub scopes: Vec<ApiKeyScope>,
224 #[serde(default)]
225 pub expires_in_days: Option<u32>,
226 pub rate_limit: Option<u32>,
227 }
228
229 pub async fn list_keys() -> impl IntoResponse {
231 (StatusCode::OK, Json(serde_json::json!({"keys": []})))
232 }
233
234 pub async fn create_key(Json(req): Json<CreateKeyRequest>) -> impl IntoResponse {
236 let raw_key = ApiKeyService::generate_key();
237 let prefix = raw_key[..11].to_string();
238
239 let response = ApiKeyCreated {
240 id: Uuid::new_v4(),
241 name: req.name,
242 key: raw_key,
243 prefix,
244 scopes: req.scopes,
245 created_at: Utc::now(),
246 expires_at: req
247 .expires_in_days
248 .map(|d| Utc::now() + chrono::Duration::days(d as i64)),
249 };
250
251 (StatusCode::CREATED, Json(response))
252 }
253
254 pub async fn revoke_key(Path(id): Path<Uuid>) -> impl IntoResponse {
256 (
257 StatusCode::OK,
258 Json(serde_json::json!({"success": true, "revoked_key_id": id})),
259 )
260 }
261
262 pub async fn rotate_key(Path(id): Path<Uuid>) -> impl IntoResponse {
264 StatusCode::NOT_IMPLEMENTED
266 }
267}