crates/server: REST API routes; RBAC auth middleware; cookie sessions; dashboard
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: I5298a925bd9c11780e49d8b1c98eebd86a6a6964
This commit is contained in:
parent
44d1ee1d6b
commit
235d3d38a6
38 changed files with 6275 additions and 7 deletions
98
crates/server/src/routes/auth.rs
Normal file
98
crates/server/src/routes/auth.rs
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
use axum::{Json, Router, extract::State, routing::get};
|
||||
use fc_common::repo;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::{Digest, Sha256};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::auth_middleware::RequireAdmin;
|
||||
use crate::error::ApiError;
|
||||
use crate::state::AppState;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct CreateApiKeyRequest {
|
||||
pub name: String,
|
||||
pub role: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct CreateApiKeyResponse {
|
||||
pub id: Uuid,
|
||||
pub name: String,
|
||||
pub key: String,
|
||||
pub role: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct ApiKeyInfo {
|
||||
pub id: Uuid,
|
||||
pub name: String,
|
||||
pub role: String,
|
||||
pub created_at: chrono::DateTime<chrono::Utc>,
|
||||
pub last_used_at: Option<chrono::DateTime<chrono::Utc>>,
|
||||
}
|
||||
|
||||
pub fn hash_api_key(key: &str) -> String {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(key.as_bytes());
|
||||
hex::encode(hasher.finalize())
|
||||
}
|
||||
|
||||
async fn create_api_key(
|
||||
_auth: RequireAdmin,
|
||||
State(state): State<AppState>,
|
||||
Json(input): Json<CreateApiKeyRequest>,
|
||||
) -> Result<Json<CreateApiKeyResponse>, ApiError> {
|
||||
let role = input.role.unwrap_or_else(|| "read-only".to_string());
|
||||
|
||||
// Generate a random API key
|
||||
let key = format!("fc_{}", Uuid::new_v4().to_string().replace('-', ""));
|
||||
let key_hash = hash_api_key(&key);
|
||||
|
||||
let api_key = repo::api_keys::create(&state.pool, &input.name, &key_hash, &role)
|
||||
.await
|
||||
.map_err(ApiError)?;
|
||||
|
||||
Ok(Json(CreateApiKeyResponse {
|
||||
id: api_key.id,
|
||||
name: api_key.name,
|
||||
key, // Only returned once at creation time
|
||||
role: api_key.role,
|
||||
}))
|
||||
}
|
||||
|
||||
async fn list_api_keys(
|
||||
_auth: RequireAdmin,
|
||||
State(state): State<AppState>,
|
||||
) -> Result<Json<Vec<ApiKeyInfo>>, ApiError> {
|
||||
let keys = repo::api_keys::list(&state.pool).await.map_err(ApiError)?;
|
||||
|
||||
let infos: Vec<ApiKeyInfo> = keys
|
||||
.into_iter()
|
||||
.map(|k| ApiKeyInfo {
|
||||
id: k.id,
|
||||
name: k.name,
|
||||
role: k.role,
|
||||
created_at: k.created_at,
|
||||
last_used_at: k.last_used_at,
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(Json(infos))
|
||||
}
|
||||
|
||||
async fn delete_api_key(
|
||||
_auth: RequireAdmin,
|
||||
State(state): State<AppState>,
|
||||
axum::extract::Path(id): axum::extract::Path<Uuid>,
|
||||
) -> Result<Json<serde_json::Value>, ApiError> {
|
||||
repo::api_keys::delete(&state.pool, id)
|
||||
.await
|
||||
.map_err(ApiError)?;
|
||||
Ok(Json(serde_json::json!({ "deleted": true })))
|
||||
}
|
||||
|
||||
pub fn router() -> Router<AppState> {
|
||||
Router::new()
|
||||
.route("/api-keys", get(list_api_keys).post(create_api_key))
|
||||
.route("/api-keys/{id}", axum::routing::delete(delete_api_key))
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue