103 lines
3 KiB
Rust
103 lines
3 KiB
Rust
use axum::{
|
|
extract::{Path, State},
|
|
http::StatusCode,
|
|
Json,
|
|
};
|
|
use serde_json::{json, Value};
|
|
|
|
use crate::{
|
|
auth::generate_api_key,
|
|
models::api_key::{ApiKey, CreateApiKey, UpdateApiKey},
|
|
state::AppState,
|
|
};
|
|
|
|
pub async fn list_api_keys(State(state): State<AppState>) -> Result<Json<Vec<ApiKey>>, StatusCode> {
|
|
let keys = sqlx::query_as::<_, ApiKey>(
|
|
"SELECT id, name, key_prefix, permissions_mask, created_at, expires_at, last_used_at
|
|
FROM api_keys ORDER BY created_at DESC",
|
|
)
|
|
.fetch_all(&state.pool)
|
|
.await
|
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
|
Ok(Json(keys))
|
|
}
|
|
|
|
pub async fn create_api_key(
|
|
State(state): State<AppState>,
|
|
Json(body): Json<CreateApiKey>,
|
|
) -> Result<Json<Value>, StatusCode> {
|
|
let (plain_key, key_prefix, key_hash) = generate_api_key();
|
|
let mask = body.permissions_mask.unwrap_or_else(|| "0".into());
|
|
|
|
sqlx::query(
|
|
"INSERT INTO api_keys (name, key_prefix, key_hash, permissions_mask, expires_at)
|
|
VALUES ($1, $2, $3, $4, $5)",
|
|
)
|
|
.bind(&body.name)
|
|
.bind(&key_prefix)
|
|
.bind(&key_hash)
|
|
.bind(&mask)
|
|
.bind(body.expires_at)
|
|
.execute(&state.pool)
|
|
.await
|
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
|
|
|
Ok(Json(json!({
|
|
"key": plain_key,
|
|
"prefix": key_prefix,
|
|
"name": body.name,
|
|
"permissions_mask": mask,
|
|
"note": "Store this key securely — it will not be shown again."
|
|
})))
|
|
}
|
|
|
|
pub async fn update_api_key(
|
|
State(state): State<AppState>,
|
|
Path(id): Path<i32>,
|
|
Json(body): Json<UpdateApiKey>,
|
|
) -> Result<Json<ApiKey>, StatusCode> {
|
|
if body.name.is_none() && body.permissions_mask.is_none() && body.expires_at.is_none() {
|
|
return Err(StatusCode::BAD_REQUEST);
|
|
}
|
|
|
|
let key = sqlx::query_as::<_, ApiKey>(
|
|
"UPDATE api_keys
|
|
SET name = COALESCE($1, name),
|
|
permissions_mask = COALESCE($2, permissions_mask),
|
|
expires_at = CASE WHEN $3::boolean THEN $4 ELSE expires_at END
|
|
WHERE id = $5
|
|
RETURNING id, name, key_prefix, permissions_mask, created_at, expires_at, last_used_at",
|
|
)
|
|
.bind(body.name.as_deref())
|
|
.bind(body.permissions_mask.as_deref())
|
|
.bind(body.expires_at.is_some())
|
|
.bind(body.expires_at)
|
|
.bind(id)
|
|
.fetch_optional(&state.pool)
|
|
.await
|
|
.map_err(|e| {
|
|
tracing::error!("update api_key {}: {}", id, e);
|
|
StatusCode::INTERNAL_SERVER_ERROR
|
|
})?
|
|
.ok_or(StatusCode::NOT_FOUND)?;
|
|
|
|
Ok(Json(key))
|
|
}
|
|
|
|
pub async fn revoke_api_key(
|
|
State(state): State<AppState>,
|
|
Path(id): Path<i32>,
|
|
) -> Result<StatusCode, StatusCode> {
|
|
let rows = sqlx::query("DELETE FROM api_keys WHERE id = $1")
|
|
.bind(id)
|
|
.execute(&state.pool)
|
|
.await
|
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
|
|
.rows_affected();
|
|
|
|
if rows == 0 {
|
|
Err(StatusCode::NOT_FOUND)
|
|
} else {
|
|
Ok(StatusCode::NO_CONTENT)
|
|
}
|
|
}
|