mirror of
https://github.com/getnora-io/nora.git
synced 2026-04-12 20:50:31 +00:00
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
This commit is contained in:
@@ -14,6 +14,7 @@ use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::AppState;
|
||||
use crate::tokens::Role;
|
||||
|
||||
/// Htpasswd-based authentication
|
||||
#[derive(Clone)]
|
||||
@@ -108,7 +109,18 @@ pub async fn auth_middleware(
|
||||
if let Some(token) = auth_header.strip_prefix("Bearer ") {
|
||||
if let Some(ref token_store) = state.tokens {
|
||||
match token_store.verify_token(token) {
|
||||
Ok(_user) => return next.run(request).await,
|
||||
Ok((_user, role)) => {
|
||||
let method = request.method().clone();
|
||||
if (method == axum::http::Method::PUT
|
||||
|| method == axum::http::Method::POST
|
||||
|| method == axum::http::Method::DELETE
|
||||
|| method == axum::http::Method::PATCH)
|
||||
&& !role.can_write()
|
||||
{
|
||||
return (StatusCode::FORBIDDEN, "Read-only token").into_response();
|
||||
}
|
||||
return next.run(request).await;
|
||||
}
|
||||
Err(_) => return unauthorized_response("Invalid or expired token"),
|
||||
}
|
||||
} else {
|
||||
@@ -175,6 +187,12 @@ pub struct CreateTokenRequest {
|
||||
#[serde(default = "default_ttl")]
|
||||
pub ttl_days: u64,
|
||||
pub description: Option<String>,
|
||||
#[serde(default = "default_role_str")]
|
||||
pub role: String,
|
||||
}
|
||||
|
||||
fn default_role_str() -> String {
|
||||
"read".to_string()
|
||||
}
|
||||
|
||||
fn default_ttl() -> u64 {
|
||||
@@ -194,6 +212,7 @@ pub struct TokenListItem {
|
||||
pub expires_at: u64,
|
||||
pub last_used: Option<u64>,
|
||||
pub description: Option<String>,
|
||||
pub role: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
@@ -227,7 +246,13 @@ async fn create_token(
|
||||
}
|
||||
};
|
||||
|
||||
match token_store.create_token(&req.username, req.ttl_days, req.description) {
|
||||
let role = match req.role.as_str() {
|
||||
"read" => Role::Read,
|
||||
"write" => Role::Write,
|
||||
"admin" => Role::Admin,
|
||||
_ => return (StatusCode::BAD_REQUEST, "Invalid role. Use: read, write, admin").into_response(),
|
||||
};
|
||||
match token_store.create_token(&req.username, req.ttl_days, req.description, role) {
|
||||
Ok(token) => Json(CreateTokenResponse {
|
||||
token,
|
||||
expires_in_days: req.ttl_days,
|
||||
@@ -271,6 +296,7 @@ async fn list_tokens(
|
||||
expires_at: t.expires_at,
|
||||
last_used: t.last_used,
|
||||
description: t.description,
|
||||
role: t.role.to_string(),
|
||||
})
|
||||
.collect();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user