RTIX / src /interfaces /http /routes /merchant /developer.rs
github-actions
deploy: clean backend production release
b198f8a
Raw
History Blame Contribute Delete
2.42 kB
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))
}
#[derive(Serialize)]
pub struct NewKeyResponse {
pub key_id: String,
pub secret: String, // Only shown once
pub name: String,
}
#[derive(Deserialize)]
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 })))
}