Spaces:
Running
Running
| use crate::domain::error::{AppError, AppResult}; | |
| use crate::domain::models::ApiKeyRecord; | |
| use crate::interfaces::http::api::AppState; | |
| use crate::interfaces::http::routes::RequireAuth; | |
| use argon2::{password_hash::SaltString, Argon2, PasswordHasher}; | |
| use axum::{ | |
| extract::{Path, State}, | |
| routing::{delete, get, post}, | |
| Json, Router, | |
| }; | |
| use rand::rngs::OsRng; | |
| use serde::{Deserialize, Serialize}; | |
| use uuid::Uuid; | |
| pub fn router() -> Router<AppState> { | |
| Router::new() | |
| .route("/keys", get(list_keys)) | |
| .route("/keys", post(create_key)) | |
| .route("/keys/:id", delete(revoke_key)) | |
| } | |
| pub struct NewKeyResponse { | |
| pub key_id: String, | |
| pub secret: String, // Only shown once | |
| pub name: String, | |
| } | |
| pub struct CreateKeyRequest { | |
| pub name: String, | |
| } | |
| async fn list_keys( | |
| RequireAuth(merchant_id): RequireAuth, | |
| State(state): State<AppState>, | |
| ) -> AppResult<Json<Vec<ApiKeyRecord>>> { | |
| let keys = ApiKeyRecord::list_for_merchant(&state.pool, &merchant_id).await?; | |
| Ok(Json(keys)) | |
| } | |
| async fn create_key( | |
| RequireAuth(merchant_id): RequireAuth, | |
| State(state): State<AppState>, | |
| Json(payload): Json<CreateKeyRequest>, | |
| ) -> AppResult<Json<NewKeyResponse>> { | |
| let key_id = format!("vk_{}", Uuid::new_v4().simple()); | |
| let secret = Uuid::new_v4().simple().to_string(); | |
| // Hash the secret | |
| let salt = SaltString::generate(&mut OsRng); | |
| let argon2 = Argon2::default(); | |
| let secret_hash = argon2 | |
| .hash_password(secret.as_bytes(), &salt) | |
| .map_err(|_| AppError::Internal("Hash failed".to_string()))? | |
| .to_string(); | |
| let record = ApiKeyRecord { | |
| key_id: key_id.clone(), | |
| merchant_id, | |
| name: payload.name.clone(), | |
| secret_hash, | |
| scopes: serde_json::json!(["read", "write"]), | |
| last_used_at: None, | |
| created_at: chrono::Utc::now().naive_utc(), | |
| }; | |
| ApiKeyRecord::create(&state.pool, &record).await?; | |
| Ok(Json(NewKeyResponse { | |
| key_id, | |
| secret, // Hand back raw secret only this once | |
| name: payload.name, | |
| })) | |
| } | |
| async fn revoke_key( | |
| RequireAuth(merchant_id): RequireAuth, | |
| Path(id): Path<String>, | |
| State(state): State<AppState>, | |
| ) -> AppResult<Json<serde_json::Value>> { | |
| ApiKeyRecord::delete(&state.pool, &id, &merchant_id).await?; | |
| Ok(Json(serde_json::json!({ "success": true }))) | |
| } | |