Files
nora/nora-registry/src/registry/cargo_registry.rs
devitway 402d2321ef feat: add RBAC (read/write/admin) and persistent audit log
- Add Role enum to tokens: Read, Write, Admin (default: Read)
- Enforce role-based access in auth middleware (read-only tokens blocked from PUT/POST/DELETE)
- Add role field to token create/list/verify API
- Add persistent audit log (append-only JSONL) for all registry operations
- Audit logging across all registries: docker, npm, maven, pypi, cargo, raw

DevITWay
2026-03-03 10:40:59 +00:00

60 lines
1.7 KiB
Rust

// Copyright (c) 2026 Volkov Pavel | DevITWay
// SPDX-License-Identifier: MIT
use crate::activity_log::{ActionType, ActivityEntry};
use crate::audit::AuditEntry;
use crate::AppState;
use axum::{
extract::{Path, State},
http::StatusCode,
response::{IntoResponse, Response},
routing::get,
Router,
};
use std::sync::Arc;
pub fn routes() -> Router<Arc<AppState>> {
Router::new()
.route("/cargo/api/v1/crates/{crate_name}", get(get_metadata))
.route(
"/cargo/api/v1/crates/{crate_name}/{version}/download",
get(download),
)
}
async fn get_metadata(
State(state): State<Arc<AppState>>,
Path(crate_name): Path<String>,
) -> Response {
let key = format!("cargo/{}/metadata.json", crate_name);
match state.storage.get(&key).await {
Ok(data) => (StatusCode::OK, data).into_response(),
Err(_) => StatusCode::NOT_FOUND.into_response(),
}
}
async fn download(
State(state): State<Arc<AppState>>,
Path((crate_name, version)): Path<(String, String)>,
) -> Response {
let key = format!(
"cargo/{}/{}/{}-{}.crate",
crate_name, version, crate_name, version
);
match state.storage.get(&key).await {
Ok(data) => {
state.metrics.record_download("cargo");
state.metrics.record_cache_hit();
state.activity.push(ActivityEntry::new(
ActionType::Pull,
format!("{}@{}", crate_name, version),
"cargo",
"LOCAL",
));
state.audit.log(AuditEntry::new("pull", "api", "", "cargo", ""));
(StatusCode::OK, data).into_response()
}
Err(_) => StatusCode::NOT_FOUND.into_response(),
}
}